Drupal: hlavičky, poměrně funkční čtení příspěvků (vlánka).
3 * see AUTHORS for the list of contributors
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.
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.
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/>.
18 package org.sonews.storage.impl;
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;
48 * @author František Kučera (frantovo.cz)
50 public class DrupalDatabase implements Storage {
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";
63 public DrupalDatabase() throws StorageBackendException {
67 private void connectDatabase() throws StorageBackendException {
69 // Load database driver
70 String driverClass = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object");
71 Class.forName(driverClass);
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);
79 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
80 if (conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
81 log.warning("Database is NOT fully serializable!");
83 } catch (Exception e) {
84 throw new StorageBackendException(e);
88 protected static void close(Connection connection, Statement statement, ResultSet resultSet) {
89 if (resultSet != null) {
92 } catch (Exception e) {
95 if (statement != null) {
98 } catch (Exception e) {
101 if (connection != null) {
104 } catch (Exception e) {
111 * @param messageID <{0}-{1}-{2}@domain.tld> 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
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("\\-");
122 private static Long parseArticleID(String messageID) {
123 String[] localPart = parseMessageID(messageID);
124 if (localPart == null) {
127 return Long.parseLong(localPart[0]);
131 private static Long parseGroupID(String messageID) {
132 String[] localPart = parseMessageID(messageID);
133 if (localPart == null) {
136 return Long.parseLong(localPart[1]);
140 private static String parseGroupName(String messageID) {
141 String[] localPart = parseMessageID(messageID);
142 if (localPart == null) {
149 private static String constructMessageId(int articleID, int groupID, String groupName, String domainName) {
150 StringBuilder sb = new StringBuilder();
152 sb.append(articleID);
156 sb.append(groupName);
158 sb.append(domainName);
160 return sb.toString();
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
171 private static void addHeader(StringBuilder sb, String key, String value, boolean encode) throws UnsupportedEncodingException {
175 sb.append(MimeUtility.encodeWord(value));
182 private String constructHeaders(ResultSet rs) throws SQLException, UnsupportedEncodingException {
183 StringBuilder sb = new StringBuilder();
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);
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);
201 return sb.toString();
205 public List<Group> getGroups() throws StorageBackendException {
206 PreparedStatement ps = null;
209 ps = conn.prepareStatement("SELECT * FROM nntp_group");
210 rs = ps.executeQuery();
211 List<Group> skupiny = new ArrayList<Group>();
214 skupiny.add(new Group(rs.getString("name"), rs.getInt("id"), Group.READONLY));
218 } catch (Exception e) {
219 throw new StorageBackendException(e);
226 public Group getGroup(String name) throws StorageBackendException {
227 PreparedStatement ps = null;
230 ps = conn.prepareStatement("SELECT * FROM nntp_group WHERE name = ?");
231 ps.setString(1, name);
232 rs = ps.executeQuery();
235 return new Group(rs.getString("name"), rs.getInt("id"), Group.READONLY);
239 } catch (Exception e) {
240 throw new StorageBackendException(e);
247 public boolean isGroupExisting(String groupname) throws StorageBackendException {
248 return getGroup(groupname) != null;
252 public Article getArticle(String messageID) throws StorageBackendException {
253 Long articleID = parseArticleID(messageID);
254 Long groupID = parseGroupID(messageID);
256 if (articleID == null || groupID == null) {
257 log.log(Level.SEVERE, "Invalid messageID: {0}", new Object[]{messageID});
260 return getArticle(articleID, groupID);
265 public Article getArticle(long articleID, long groupID) throws StorageBackendException {
266 PreparedStatement ps = null;
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();
275 String headers = constructHeaders(rs);
277 BCodec bc = new BCodec(CHARSET);
278 byte[] body = qpc.encode(rs.getString("text")).getBytes();
279 //byte[] body = bc.encode(rs.getString("text")).getBytes();
281 return new Article(headers, body);
285 } catch (Exception e) {
286 throw new StorageBackendException(e);
293 public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first, long last) throws StorageBackendException {
294 PreparedStatement ps = null;
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);
302 rs = ps.executeQuery();
304 List<Pair<Long, ArticleHead>> heads = new ArrayList<Pair<Long, ArticleHead>>();
307 String headers = constructHeaders(rs);
308 heads.add(new Pair<Long, ArticleHead>(rs.getLong("id"), new ArticleHead(headers)));
312 } catch (Exception e) {
313 throw new StorageBackendException(e);
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});
323 return Collections.emptyList();
327 public long getArticleIndex(Article article, Group group) throws StorageBackendException {
328 Long id = parseArticleID(article.getMessageID());
330 throw new StorageBackendException("Invalid messageID: " + article.getMessageID());
337 public List<Long> getArticleNumbers(long groupID) throws StorageBackendException {
338 PreparedStatement ps = null;
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>();
346 articleNumbers.add(rs.getLong(1));
348 return articleNumbers;
349 } catch (Exception e) {
350 throw new StorageBackendException(e);
357 public int getFirstArticleNumber(Group group) throws StorageBackendException {
358 PreparedStatement ps = null;
361 ps = conn.prepareStatement("SELECT min(id) FROM nntp_article WHERE group_id = ?");
362 ps.setLong(1, group.getInternalID());
363 rs = ps.executeQuery();
366 } catch (Exception e) {
367 throw new StorageBackendException(e);
374 public int getLastArticleNumber(Group group) throws StorageBackendException {
375 PreparedStatement ps = null;
378 ps = conn.prepareStatement("SELECT max(id) FROM nntp_article WHERE group_id = ?");
379 ps.setLong(1, group.getInternalID());
380 rs = ps.executeQuery();
383 } catch (Exception e) {
384 throw new StorageBackendException(e);
391 public boolean isArticleExisting(String messageID) throws StorageBackendException {
392 Long articleID = parseArticleID(messageID);
393 Long groupID = parseGroupID(messageID);
395 if (articleID == null || groupID == null) {
398 PreparedStatement ps = null;
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();
407 return rs.getInt(1) == 1;
408 } catch (Exception e) {
409 throw new StorageBackendException(e);
417 // --- zatím neimplementovat ---
420 public void addArticle(Article art) throws StorageBackendException {
421 log.log(Level.SEVERE, "TODO: addArticle {0}", new Object[]{art});
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});
430 public void addGroup(String groupname, int flags) throws StorageBackendException {
431 log.log(Level.SEVERE, "TODO: addGroup {0} / {1}", new Object[]{groupname, flags});
435 public int countArticles() throws StorageBackendException {
436 PreparedStatement ps = null;
439 ps = conn.prepareStatement("SELECT count(*) FROM nntp_article");
440 rs = ps.executeQuery();
443 } catch (Exception e) {
444 throw new StorageBackendException(e);
451 public int countGroups() throws StorageBackendException {
452 PreparedStatement ps = null;
455 ps = conn.prepareStatement("SELECT count(*) FROM nntp_group");
456 rs = ps.executeQuery();
459 } catch (Exception e) {
460 throw new StorageBackendException(e);
467 public void delete(String messageID) throws StorageBackendException {
468 log.log(Level.SEVERE, "TODO: delete {0}", new Object[]{messageID});
472 public String getConfigValue(String key) throws StorageBackendException {
473 //log.log(Level.SEVERE, "TODO: getConfigValue {0}", new Object[]{key});
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});
484 public double getEventsPerHour(int key, long gid) throws StorageBackendException {
485 log.log(Level.SEVERE, "TODO: getEventsPerHour {0} / {1}", new Object[]{key, gid});
490 public List<String> getGroupsForList(String listAddress) throws StorageBackendException {
491 log.log(Level.SEVERE, "TODO: getGroupsForList {0}", new Object[]{listAddress});
492 return Collections.emptyList();
496 public List<String> getListsForGroup(String groupname) throws StorageBackendException {
497 log.log(Level.SEVERE, "TODO: getListsForGroup {0}", new Object[]{groupname});
498 return Collections.emptyList();
502 public String getOldestArticle() throws StorageBackendException {
503 log.log(Level.SEVERE, "TODO: getOldestArticle");
508 public int getPostingsCount(String groupname) throws StorageBackendException {
509 PreparedStatement ps = null;
512 ps = conn.prepareStatement("SELECT count(*) FROM nntp_article WHERE group_name = ?");
513 ps.setString(1, groupname);
514 rs = ps.executeQuery();
517 } catch (Exception e) {
518 throw new StorageBackendException(e);
525 public List<Subscription> getSubscriptions(int type) throws StorageBackendException {
526 log.log(Level.SEVERE, "TODO: getSubscriptions {0}", new Object[]{type});
527 return Collections.emptyList();
531 public void purgeGroup(Group group) throws StorageBackendException {
532 log.log(Level.SEVERE, "TODO: purgeGroup {0}", new Object[]{group});
536 public void setConfigValue(String key, String value) throws StorageBackendException {
537 log.log(Level.SEVERE, "TODO: setConfigValue {0} = {1}", new Object[]{key, value});
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.");
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.");