src/org/sonews/storage/impl/DrupalDatabase.java
author František Kučera <franta-hg@frantovo.cz>
Tue Oct 11 16:17:51 2011 +0200 (2011-10-11)
changeset 70 2177f9b14688
parent 69 b51612c18a54
child 71 beb11d70f0eb
permissions -rw-r--r--
Drupal: hlavičky, poměrně funkční čtení příspěvků (vlánka).
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 package org.sonews.storage.impl;
    19 
    20 import java.io.UnsupportedEncodingException;
    21 import java.sql.Connection;
    22 import java.sql.DriverManager;
    23 import java.sql.PreparedStatement;
    24 import java.sql.ResultSet;
    25 import java.sql.SQLException;
    26 import java.sql.Statement;
    27 import java.util.ArrayList;
    28 import java.util.Collections;
    29 import java.util.Date;
    30 import java.util.List;
    31 import java.util.logging.Level;
    32 import java.util.logging.Logger;
    33 import javax.mail.internet.MailDateFormat;
    34 import javax.mail.internet.MimeUtility;
    35 import org.apache.commons.codec.net.BCodec;
    36 import org.apache.commons.codec.net.QuotedPrintableCodec;
    37 import org.sonews.config.Config;
    38 import org.sonews.feed.Subscription;
    39 import org.sonews.storage.Article;
    40 import org.sonews.storage.ArticleHead;
    41 import org.sonews.storage.Group;
    42 import org.sonews.storage.Storage;
    43 import org.sonews.storage.StorageBackendException;
    44 import org.sonews.util.Pair;
    45 
    46 /**
    47  *
    48  * @author František Kučera (frantovo.cz)
    49  */
    50 public class DrupalDatabase implements Storage {
    51 
    52 	private static final Logger log = Logger.getLogger(DrupalDatabase.class.getName());
    53 	public static final String CHARSET = "UTF-8";
    54 	public static final String CRLF = "\r\n";
    55 	public static final int MAX_RESTARTS = 2;
    56 	/** How many times the database connection was reinitialized */
    57 	protected int restarts = 0;
    58 	protected Connection conn = null;
    59 	private QuotedPrintableCodec qpc = new QuotedPrintableCodec(CHARSET);
    60 	// TODO: správná doména
    61 	private String myDomain = "kinderporno.cz";
    62 
    63 	public DrupalDatabase() throws StorageBackendException {
    64 		connectDatabase();
    65 	}
    66 
    67 	private void connectDatabase() throws StorageBackendException {
    68 		try {
    69 			// Load database driver
    70 			String driverClass = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object");
    71 			Class.forName(driverClass);
    72 
    73 			// Establish database connection
    74 			String url = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>");
    75 			String username = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root");
    76 			String password = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, "");
    77 			conn = DriverManager.getConnection(url, username, password);
    78 
    79 			conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
    80 			if (conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
    81 				log.warning("Database is NOT fully serializable!");
    82 			}
    83 		} catch (Exception e) {
    84 			throw new StorageBackendException(e);
    85 		}
    86 	}
    87 
    88 	protected static void close(Connection connection, Statement statement, ResultSet resultSet) {
    89 		if (resultSet != null) {
    90 			try {
    91 				resultSet.close();
    92 			} catch (Exception e) {
    93 			}
    94 		}
    95 		if (statement != null) {
    96 			try {
    97 				statement.close();
    98 			} catch (Exception e) {
    99 			}
   100 		}
   101 		if (connection != null) {
   102 			try {
   103 				connection.close();
   104 			} catch (Exception e) {
   105 			}
   106 		}
   107 	}
   108 
   109 	/**
   110 	 * 
   111 	 * @param messageID &lt;{0}-{1}-{2}@domain.tld&gt; where {0} is nntp_id and {1} is group_id and {2} is group_name
   112 	 * @return array where [0] = nntp_id and [1] = group_id and [2] = group_name or returns null if messageID is invalid
   113 	 */
   114 	private static String[] parseMessageID(String messageID) {
   115 		if (messageID.matches("<[0-9]+\\-[0-9]+\\-[a-z0-9\\.]+@.+>")) {
   116 			return messageID.substring(1).split("@")[0].split("\\-");
   117 		} else {
   118 			return null;
   119 		}
   120 	}
   121 
   122 	private static Long parseArticleID(String messageID) {
   123 		String[] localPart = parseMessageID(messageID);
   124 		if (localPart == null) {
   125 			return null;
   126 		} else {
   127 			return Long.parseLong(localPart[0]);
   128 		}
   129 	}
   130 
   131 	private static Long parseGroupID(String messageID) {
   132 		String[] localPart = parseMessageID(messageID);
   133 		if (localPart == null) {
   134 			return null;
   135 		} else {
   136 			return Long.parseLong(localPart[1]);
   137 		}
   138 	}
   139 
   140 	private static String parseGroupName(String messageID) {
   141 		String[] localPart = parseMessageID(messageID);
   142 		if (localPart == null) {
   143 			return null;
   144 		} else {
   145 			return localPart[2];
   146 		}
   147 	}
   148 
   149 	private static String constructMessageId(int articleID, int groupID, String groupName, String domainName) {
   150 		StringBuilder sb = new StringBuilder();
   151 		sb.append("<");
   152 		sb.append(articleID);
   153 		sb.append("-");
   154 		sb.append(groupID);
   155 		sb.append("-");
   156 		sb.append(groupName);
   157 		sb.append("@");
   158 		sb.append(domainName);
   159 		sb.append(">");
   160 		return sb.toString();
   161 	}
   162 
   163 	/**
   164 	 * 
   165 	 * @param sb header list to be appended with new header. List must be terminated by line end.
   166 	 * @param key header name (without : and space)
   167 	 * @param value header value
   168 	 * @param encode true if value should be encoded/escaped before appending
   169 	 * @throws UnsupportedEncodingException 
   170 	 */
   171 	private static void addHeader(StringBuilder sb, String key, String value, boolean encode) throws UnsupportedEncodingException {
   172 		sb.append(key);
   173 		sb.append(": ");
   174 		if (encode) {
   175 			sb.append(MimeUtility.encodeWord(value));
   176 		} else {
   177 			sb.append(value);
   178 		}
   179 		sb.append(CRLF);
   180 	}
   181 
   182 	private String constructHeaders(ResultSet rs) throws SQLException, UnsupportedEncodingException {
   183 		StringBuilder sb = new StringBuilder();
   184 
   185 		addHeader(sb, "Message-id", constructMessageId(rs.getInt("id"), rs.getInt("group_id"), rs.getString("group_name"), myDomain), false);
   186 		addHeader(sb, "From", MimeUtility.encodeWord(rs.getString("sender_name")) + " <>", false);
   187 		addHeader(sb, "Subject", rs.getString("subject"), true);
   188 		/** TODO: správný formát data: */
   189 		addHeader(sb, "Date", MailDateFormat.getInstance().format(new Date(rs.getLong("created") * 1000)), false);
   190 		addHeader(sb, "Content-Type", "text/html; charset=" + CHARSET, false);
   191 		addHeader(sb, "Content-Transfer-Encoding", "quoted-printable", false);
   192 		//addHeader(sb, "Content-Transfer-Encoding", "base64", false);
   193 
   194 		Integer parentID = rs.getInt("parent_id");
   195 		if (parentID != null && parentID > 0) {
   196 			String parentMessageID = constructMessageId(parentID, rs.getInt("group_id"), rs.getString("group_name"), myDomain);
   197 			addHeader(sb, "In-Reply-To", parentMessageID, false);
   198 			addHeader(sb, "References", parentMessageID, false);
   199 		}
   200 
   201 		return sb.toString();
   202 	}
   203 
   204 	@Override
   205 	public List<Group> getGroups() throws StorageBackendException {
   206 		PreparedStatement ps = null;
   207 		ResultSet rs = null;
   208 		try {
   209 			ps = conn.prepareStatement("SELECT * FROM nntp_group");
   210 			rs = ps.executeQuery();
   211 			List<Group> skupiny = new ArrayList<Group>();
   212 
   213 			while (rs.next()) {
   214 				skupiny.add(new Group(rs.getString("name"), rs.getInt("id"), Group.READONLY));
   215 			}
   216 
   217 			return skupiny;
   218 		} catch (Exception e) {
   219 			throw new StorageBackendException(e);
   220 		} finally {
   221 			close(null, ps, rs);
   222 		}
   223 	}
   224 
   225 	@Override
   226 	public Group getGroup(String name) throws StorageBackendException {
   227 		PreparedStatement ps = null;
   228 		ResultSet rs = null;
   229 		try {
   230 			ps = conn.prepareStatement("SELECT * FROM nntp_group WHERE name = ?");
   231 			ps.setString(1, name);
   232 			rs = ps.executeQuery();
   233 
   234 			while (rs.next()) {
   235 				return new Group(rs.getString("name"), rs.getInt("id"), Group.READONLY);
   236 			}
   237 
   238 			return null;
   239 		} catch (Exception e) {
   240 			throw new StorageBackendException(e);
   241 		} finally {
   242 			close(null, ps, rs);
   243 		}
   244 	}
   245 
   246 	@Override
   247 	public boolean isGroupExisting(String groupname) throws StorageBackendException {
   248 		return getGroup(groupname) != null;
   249 	}
   250 
   251 	@Override
   252 	public Article getArticle(String messageID) throws StorageBackendException {
   253 		Long articleID = parseArticleID(messageID);
   254 		Long groupID = parseGroupID(messageID);
   255 
   256 		if (articleID == null || groupID == null) {
   257 			log.log(Level.SEVERE, "Invalid messageID: {0}", new Object[]{messageID});
   258 			return null;
   259 		} else {
   260 			return getArticle(articleID, groupID);
   261 		}
   262 	}
   263 
   264 	@Override
   265 	public Article getArticle(long articleID, long groupID) throws StorageBackendException {
   266 		PreparedStatement ps = null;
   267 		ResultSet rs = null;
   268 		try {
   269 			ps = conn.prepareStatement("SELECT * FROM nntp_article WHERE id = ? AND group_id = ?");
   270 			ps.setLong(1, articleID);
   271 			ps.setLong(2, groupID);
   272 			rs = ps.executeQuery();
   273 
   274 			if (rs.next()) {
   275 				String headers = constructHeaders(rs);
   276 				// TODO: fold?
   277 				BCodec bc = new BCodec(CHARSET);
   278 				byte[] body = qpc.encode(rs.getString("text")).getBytes();
   279 				//byte[] body = bc.encode(rs.getString("text")).getBytes();
   280 
   281 				return new Article(headers, body);
   282 			} else {
   283 				return null;
   284 			}
   285 		} catch (Exception e) {
   286 			throw new StorageBackendException(e);
   287 		} finally {
   288 			close(null, ps, rs);
   289 		}
   290 	}
   291 
   292 	@Override
   293 	public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last) throws StorageBackendException {
   294 		PreparedStatement ps = null;
   295 		ResultSet rs = null;
   296 		try {
   297 			// TODO: je nutné řazení?
   298 			ps = conn.prepareStatement("SELECT * FROM nntp_article WHERE group_id = ? AND id >= ? AND id <= ? ORDER BY id");
   299 			ps.setLong(1, group.getInternalID());
   300 			ps.setLong(2, first);
   301 			ps.setLong(3, last);
   302 			rs = ps.executeQuery();
   303 
   304 			List<Pair<Long, ArticleHead>> heads = new ArrayList<Pair<Long, ArticleHead>>();
   305 
   306 			while (rs.next()) {
   307 				String headers = constructHeaders(rs);
   308 				heads.add(new Pair<Long, ArticleHead>(rs.getLong("id"), new ArticleHead(headers)));
   309 			}
   310 
   311 			return heads;
   312 		} catch (Exception e) {
   313 			throw new StorageBackendException(e);
   314 		} finally {
   315 			close(null, ps, rs);
   316 		}
   317 	}
   318 
   319 	@Override
   320 	public List<Pair<Long, String>> getArticleHeaders(Group group, long start, long end, String header, String pattern) throws StorageBackendException {
   321 		log.log(Level.SEVERE, "TODO: getArticleHeaders {0} / {1} / {2} / {3} / {4}", new Object[]{group, start, end, header, pattern});
   322 		/** TODO: */
   323 		return Collections.emptyList();
   324 	}
   325 
   326 	@Override
   327 	public long getArticleIndex(Article article, Group group) throws StorageBackendException {
   328 		Long id = parseArticleID(article.getMessageID());
   329 		if (id == null) {
   330 			throw new StorageBackendException("Invalid messageID: " + article.getMessageID());
   331 		} else {
   332 			return id;
   333 		}
   334 	}
   335 
   336 	@Override
   337 	public List<Long> getArticleNumbers(long groupID) throws StorageBackendException {
   338 		PreparedStatement ps = null;
   339 		ResultSet rs = null;
   340 		try {
   341 			ps = conn.prepareStatement("SELECT id FROM nntp_article WHERE group_id = ?");
   342 			ps.setLong(1, groupID);
   343 			rs = ps.executeQuery();
   344 			List<Long> articleNumbers = new ArrayList<Long>();
   345 			while (rs.next()) {
   346 				articleNumbers.add(rs.getLong(1));
   347 			}
   348 			return articleNumbers;
   349 		} catch (Exception e) {
   350 			throw new StorageBackendException(e);
   351 		} finally {
   352 			close(null, ps, rs);
   353 		}
   354 	}
   355 
   356 	@Override
   357 	public int getFirstArticleNumber(Group group) throws StorageBackendException {
   358 		PreparedStatement ps = null;
   359 		ResultSet rs = null;
   360 		try {
   361 			ps = conn.prepareStatement("SELECT min(id) FROM nntp_article WHERE group_id = ?");
   362 			ps.setLong(1, group.getInternalID());
   363 			rs = ps.executeQuery();
   364 			rs.next();
   365 			return rs.getInt(1);
   366 		} catch (Exception e) {
   367 			throw new StorageBackendException(e);
   368 		} finally {
   369 			close(null, ps, rs);
   370 		}
   371 	}
   372 
   373 	@Override
   374 	public int getLastArticleNumber(Group group) throws StorageBackendException {
   375 		PreparedStatement ps = null;
   376 		ResultSet rs = null;
   377 		try {
   378 			ps = conn.prepareStatement("SELECT max(id) FROM nntp_article WHERE group_id = ?");
   379 			ps.setLong(1, group.getInternalID());
   380 			rs = ps.executeQuery();
   381 			rs.next();
   382 			return rs.getInt(1);
   383 		} catch (Exception e) {
   384 			throw new StorageBackendException(e);
   385 		} finally {
   386 			close(null, ps, rs);
   387 		}
   388 	}
   389 
   390 	@Override
   391 	public boolean isArticleExisting(String messageID) throws StorageBackendException {
   392 		Long articleID = parseArticleID(messageID);
   393 		Long groupID = parseGroupID(messageID);
   394 
   395 		if (articleID == null || groupID == null) {
   396 			return false;
   397 		} else {
   398 			PreparedStatement ps = null;
   399 			ResultSet rs = null;
   400 			try {
   401 				ps = conn.prepareStatement("SELECT count(*) FROM nntp_article WHERE id = ? AND group_id = ?");
   402 				ps.setLong(1, articleID);
   403 				ps.setLong(2, groupID);
   404 				rs = ps.executeQuery();
   405 
   406 				rs.next();
   407 				return rs.getInt(1) == 1;
   408 			} catch (Exception e) {
   409 				throw new StorageBackendException(e);
   410 			} finally {
   411 				close(null, ps, rs);
   412 			}
   413 		}
   414 	}
   415 
   416 	//
   417 	// --- zatím neimplementovat ---
   418 	//
   419 	@Override
   420 	public void addArticle(Article art) throws StorageBackendException {
   421 		log.log(Level.SEVERE, "TODO: addArticle {0}", new Object[]{art});
   422 	}
   423 
   424 	@Override
   425 	public void addEvent(long timestamp, int type, long groupID) throws StorageBackendException {
   426 		log.log(Level.SEVERE, "TODO: addEvent {0} / {1} / {2}", new Object[]{timestamp, type, groupID});
   427 	}
   428 
   429 	@Override
   430 	public void addGroup(String groupname, int flags) throws StorageBackendException {
   431 		log.log(Level.SEVERE, "TODO: addGroup {0} / {1}", new Object[]{groupname, flags});
   432 	}
   433 
   434 	@Override
   435 	public int countArticles() throws StorageBackendException {
   436 		PreparedStatement ps = null;
   437 		ResultSet rs = null;
   438 		try {
   439 			ps = conn.prepareStatement("SELECT count(*) FROM nntp_article");
   440 			rs = ps.executeQuery();
   441 			rs.next();
   442 			return rs.getInt(1);
   443 		} catch (Exception e) {
   444 			throw new StorageBackendException(e);
   445 		} finally {
   446 			close(null, ps, rs);
   447 		}
   448 	}
   449 
   450 	@Override
   451 	public int countGroups() throws StorageBackendException {
   452 		PreparedStatement ps = null;
   453 		ResultSet rs = null;
   454 		try {
   455 			ps = conn.prepareStatement("SELECT count(*) FROM nntp_group");
   456 			rs = ps.executeQuery();
   457 			rs.next();
   458 			return rs.getInt(1);
   459 		} catch (Exception e) {
   460 			throw new StorageBackendException(e);
   461 		} finally {
   462 			close(null, ps, rs);
   463 		}
   464 	}
   465 
   466 	@Override
   467 	public void delete(String messageID) throws StorageBackendException {
   468 		log.log(Level.SEVERE, "TODO: delete {0}", new Object[]{messageID});
   469 	}
   470 
   471 	@Override
   472 	public String getConfigValue(String key) throws StorageBackendException {
   473 		//log.log(Level.SEVERE, "TODO: getConfigValue {0}", new Object[]{key});
   474 		return null;
   475 	}
   476 
   477 	@Override
   478 	public int getEventsCount(int eventType, long startTimestamp, long endTimestamp, Group group) throws StorageBackendException {
   479 		log.log(Level.SEVERE, "TODO: getEventsCount {0} / {1} / {2} / {3}", new Object[]{eventType, startTimestamp, endTimestamp, group});
   480 		return 0;
   481 	}
   482 
   483 	@Override
   484 	public double getEventsPerHour(int key, long gid) throws StorageBackendException {
   485 		log.log(Level.SEVERE, "TODO: getEventsPerHour {0} / {1}", new Object[]{key, gid});
   486 		return 0;
   487 	}
   488 
   489 	@Override
   490 	public List<String> getGroupsForList(String listAddress) throws StorageBackendException {
   491 		log.log(Level.SEVERE, "TODO: getGroupsForList {0}", new Object[]{listAddress});
   492 		return Collections.emptyList();
   493 	}
   494 
   495 	@Override
   496 	public List<String> getListsForGroup(String groupname) throws StorageBackendException {
   497 		log.log(Level.SEVERE, "TODO: getListsForGroup {0}", new Object[]{groupname});
   498 		return Collections.emptyList();
   499 	}
   500 
   501 	@Override
   502 	public String getOldestArticle() throws StorageBackendException {
   503 		log.log(Level.SEVERE, "TODO: getOldestArticle");
   504 		return null;
   505 	}
   506 
   507 	@Override
   508 	public int getPostingsCount(String groupname) throws StorageBackendException {
   509 		PreparedStatement ps = null;
   510 		ResultSet rs = null;
   511 		try {
   512 			ps = conn.prepareStatement("SELECT count(*) FROM nntp_article WHERE group_name = ?");
   513 			ps.setString(1, groupname);
   514 			rs = ps.executeQuery();
   515 			rs.next();
   516 			return rs.getInt(1);
   517 		} catch (Exception e) {
   518 			throw new StorageBackendException(e);
   519 		} finally {
   520 			close(null, ps, rs);
   521 		}
   522 	}
   523 
   524 	@Override
   525 	public List<Subscription> getSubscriptions(int type) throws StorageBackendException {
   526 		log.log(Level.SEVERE, "TODO: getSubscriptions {0}", new Object[]{type});
   527 		return Collections.emptyList();
   528 	}
   529 
   530 	@Override
   531 	public void purgeGroup(Group group) throws StorageBackendException {
   532 		log.log(Level.SEVERE, "TODO: purgeGroup {0}", new Object[]{group});
   533 	}
   534 
   535 	@Override
   536 	public void setConfigValue(String key, String value) throws StorageBackendException {
   537 		log.log(Level.SEVERE, "TODO: setConfigValue {0} = {1}", new Object[]{key, value});
   538 	}
   539 
   540 	@Override
   541 	public boolean update(Article article) throws StorageBackendException {
   542 		log.log(Level.SEVERE, "TODO: update {0}", new Object[]{article});
   543 		throw new StorageBackendException("Not implemented yet.");
   544 	}
   545 
   546 	@Override
   547 	public boolean update(Group group) throws StorageBackendException {
   548 		log.log(Level.SEVERE, "TODO: update {0}", new Object[]{group});
   549 		throw new StorageBackendException("Not implemented yet.");
   550 	}
   551 }