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/>.
19 package org.sonews.storage.impl;
21 import java.sql.Connection;
22 import java.sql.DriverManager;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.sql.PreparedStatement;
27 import java.util.ArrayList;
28 import java.util.Enumeration;
29 import java.util.List;
30 import java.util.logging.Level;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import java.util.regex.PatternSyntaxException;
34 import javax.mail.Header;
35 import javax.mail.internet.MimeUtility;
36 import org.sonews.config.Config;
37 import org.sonews.util.Log;
38 import org.sonews.feed.Subscription;
39 import org.sonews.storage.Article;
40 import org.sonews.storage.ArticleHead;
41 import org.sonews.storage.Channel;
42 import org.sonews.storage.Group;
43 import org.sonews.storage.Storage;
44 import org.sonews.storage.StorageBackendException;
45 import org.sonews.util.Pair;
48 * JDBCDatabase facade class.
49 * @author Christian Lins
52 // TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
53 public class JDBCDatabase implements Storage
56 public static final int MAX_RESTARTS = 2;
57 protected Connection conn = null;
58 protected PreparedStatement pstmtAddArticle1 = null;
59 protected PreparedStatement pstmtAddArticle2 = null;
60 protected PreparedStatement pstmtAddArticle3 = null;
61 protected PreparedStatement pstmtAddArticle4 = null;
62 protected PreparedStatement pstmtAddGroup0 = null;
63 protected PreparedStatement pstmtAddEvent = null;
64 protected PreparedStatement pstmtCountArticles = null;
65 protected PreparedStatement pstmtCountGroups = null;
66 protected PreparedStatement pstmtDeleteArticle0 = null;
67 protected PreparedStatement pstmtDeleteArticle1 = null;
68 protected PreparedStatement pstmtDeleteArticle2 = null;
69 protected PreparedStatement pstmtDeleteArticle3 = null;
70 protected PreparedStatement pstmtGetArticle0 = null;
71 protected PreparedStatement pstmtGetArticle1 = null;
72 protected PreparedStatement pstmtGetArticleHeaders0 = null;
73 protected PreparedStatement pstmtGetArticleHeaders1 = null;
74 protected PreparedStatement pstmtGetArticleHeads = null;
75 protected PreparedStatement pstmtGetArticleIDs = null;
76 protected PreparedStatement pstmtGetArticleIndex = null;
77 protected PreparedStatement pstmtGetConfigValue = null;
78 protected PreparedStatement pstmtGetEventsCount0 = null;
79 protected PreparedStatement pstmtGetEventsCount1 = null;
80 protected PreparedStatement pstmtGetGroupForList = null;
81 protected PreparedStatement pstmtGetGroup0 = null;
82 protected PreparedStatement pstmtGetGroup1 = null;
83 protected PreparedStatement pstmtGetFirstArticleNumber = null;
84 protected PreparedStatement pstmtGetListForGroup = null;
85 protected PreparedStatement pstmtGetLastArticleNumber = null;
86 protected PreparedStatement pstmtGetMaxArticleID = null;
87 protected PreparedStatement pstmtGetMaxArticleIndex = null;
88 protected PreparedStatement pstmtGetOldestArticle = null;
89 protected PreparedStatement pstmtGetPostingsCount = null;
90 protected PreparedStatement pstmtGetSubscriptions = null;
91 protected PreparedStatement pstmtIsArticleExisting = null;
92 protected PreparedStatement pstmtIsGroupExisting = null;
93 protected PreparedStatement pstmtPurgeGroup0 = null;
94 protected PreparedStatement pstmtPurgeGroup1 = null;
95 protected PreparedStatement pstmtSetConfigValue0 = null;
96 protected PreparedStatement pstmtSetConfigValue1 = null;
97 protected PreparedStatement pstmtUpdateGroup = null;
98 /** How many times the database connection was reinitialized */
99 protected int restarts = 0;
101 protected void prepareAddGroupStatement() throws SQLException {
102 this.pstmtAddGroup0 = conn.prepareStatement(
103 "INSERT INTO groups (name, flags) VALUES (?, ?)");
106 protected void prepareCountGroupsStatement() throws SQLException {
107 this.pstmtCountGroups = conn.prepareStatement(
108 "SELECT Count(group_id) FROM groups WHERE "
109 + "flags & " + Channel.DELETED + " = 0");
112 protected void prepareGetPostingsCountStatement() throws SQLException {
113 this.pstmtGetPostingsCount = conn.prepareStatement(
114 "SELECT Count(*) FROM postings NATURAL JOIN groups "
115 + "WHERE groups.name = ?");
118 protected void prepareGetSubscriptionsStatement() throws SQLException {
119 this.pstmtGetSubscriptions = conn.prepareStatement(
120 "SELECT host, port, name FROM peers NATURAL JOIN "
121 + "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
125 * Rises the database: reconnect and recreate all prepared statements.
126 * @throws java.lang.SQLException
128 protected void arise()
132 // Load database driver
134 Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
136 // Establish database connection
137 this.conn = DriverManager.getConnection(
138 Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_DATABASE, "<not specified>"),
139 Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_USER, "root"),
140 Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""));
142 this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
143 if (this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) {
144 Log.get().warning("Database is NOT fully serializable!");
147 // Prepare statements for method addArticle()
148 this.pstmtAddArticle1 = conn.prepareStatement(
149 "INSERT INTO articles (article_id, body) VALUES(?, ?)");
150 this.pstmtAddArticle2 = conn.prepareStatement(
151 "INSERT INTO headers (article_id, header_key, header_value, header_index) "
152 + "VALUES (?, ?, ?, ?)");
153 this.pstmtAddArticle3 = conn.prepareStatement(
154 "INSERT INTO postings (group_id, article_id, article_index)"
155 + "VALUES (?, ?, ?)");
156 this.pstmtAddArticle4 = conn.prepareStatement(
157 "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
159 // Prepare statement for method addStatValue()
160 this.pstmtAddEvent = conn.prepareStatement(
161 "INSERT INTO events VALUES (?, ?, ?)");
163 // Prepare statement for method addGroup()
164 prepareAddGroupStatement();
166 // Prepare statement for method countArticles()
167 this.pstmtCountArticles = conn.prepareStatement(
168 "SELECT Count(article_id) FROM article_ids");
170 // Prepare statement for method countGroups()
171 prepareCountGroupsStatement();
173 // Prepare statements for method delete(article)
174 this.pstmtDeleteArticle0 = conn.prepareStatement(
175 "DELETE FROM articles WHERE article_id = "
176 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
177 this.pstmtDeleteArticle1 = conn.prepareStatement(
178 "DELETE FROM headers WHERE article_id = "
179 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
180 this.pstmtDeleteArticle2 = conn.prepareStatement(
181 "DELETE FROM postings WHERE article_id = "
182 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
183 this.pstmtDeleteArticle3 = conn.prepareStatement(
184 "DELETE FROM article_ids WHERE message_id = ?");
186 // Prepare statements for methods getArticle()
187 this.pstmtGetArticle0 = conn.prepareStatement(
188 "SELECT * FROM articles WHERE article_id = "
189 + "(SELECT article_id FROM article_ids WHERE message_id = ?)");
190 this.pstmtGetArticle1 = conn.prepareStatement(
191 "SELECT * FROM articles WHERE article_id = "
192 + "(SELECT article_id FROM postings WHERE "
193 + "article_index = ? AND group_id = ?)");
195 // Prepare statement for method getArticleHeaders()
196 this.pstmtGetArticleHeaders0 = conn.prepareStatement(
197 "SELECT header_key, header_value FROM headers WHERE article_id = ? "
198 + "ORDER BY header_index ASC");
200 // Prepare statement for method getArticleHeaders(regular expr pattern)
201 this.pstmtGetArticleHeaders1 = conn.prepareStatement(
202 "SELECT p.article_index, h.header_value FROM headers h "
203 + "INNER JOIN postings p ON h.article_id = p.article_id "
204 + "INNER JOIN groups g ON p.group_id = g.group_id "
205 + "WHERE g.name = ? AND "
206 + "h.header_key = ? AND "
207 + "p.article_index >= ? "
208 + "ORDER BY p.article_index ASC");
210 this.pstmtGetArticleIDs = conn.prepareStatement(
211 "SELECT article_index FROM postings WHERE group_id = ?");
213 // Prepare statement for method getArticleIndex
214 this.pstmtGetArticleIndex = conn.prepareStatement(
215 "SELECT article_index FROM postings WHERE "
216 + "article_id = (SELECT article_id FROM article_ids "
217 + "WHERE message_id = ?) "
218 + " AND group_id = ?");
220 // Prepare statements for method getArticleHeads()
221 this.pstmtGetArticleHeads = conn.prepareStatement(
222 "SELECT article_id, article_index FROM postings WHERE "
223 + "postings.group_id = ? AND article_index >= ? AND "
224 + "article_index <= ?");
226 // Prepare statements for method getConfigValue()
227 this.pstmtGetConfigValue = conn.prepareStatement(
228 "SELECT config_value FROM config WHERE config_key = ?");
230 // Prepare statements for method getEventsCount()
231 this.pstmtGetEventsCount0 = conn.prepareStatement(
232 "SELECT Count(*) FROM events WHERE event_key = ? AND "
233 + "event_time >= ? AND event_time < ?");
235 this.pstmtGetEventsCount1 = conn.prepareStatement(
236 "SELECT Count(*) FROM events WHERE event_key = ? AND "
237 + "event_time >= ? AND event_time < ? AND group_id = ?");
239 // Prepare statement for method getGroupForList()
240 this.pstmtGetGroupForList = conn.prepareStatement(
241 "SELECT name FROM groups INNER JOIN groups2list "
242 + "ON groups.group_id = groups2list.group_id "
243 + "WHERE groups2list.listaddress = ?");
245 // Prepare statement for method getGroup()
246 this.pstmtGetGroup0 = conn.prepareStatement(
247 "SELECT group_id, flags FROM groups WHERE Name = ?");
248 this.pstmtGetGroup1 = conn.prepareStatement(
249 "SELECT name FROM groups WHERE group_id = ?");
251 // Prepare statement for method getLastArticleNumber()
252 this.pstmtGetLastArticleNumber = conn.prepareStatement(
253 "SELECT Max(article_index) FROM postings WHERE group_id = ?");
255 // Prepare statement for method getListForGroup()
256 this.pstmtGetListForGroup = conn.prepareStatement(
257 "SELECT listaddress FROM groups2list INNER JOIN groups "
258 + "ON groups.group_id = groups2list.group_id WHERE name = ?");
260 // Prepare statement for method getMaxArticleID()
261 this.pstmtGetMaxArticleID = conn.prepareStatement(
262 "SELECT Max(article_id) FROM articles");
264 // Prepare statement for method getMaxArticleIndex()
265 this.pstmtGetMaxArticleIndex = conn.prepareStatement(
266 "SELECT Max(article_index) FROM postings WHERE group_id = ?");
268 // Prepare statement for method getOldestArticle()
269 this.pstmtGetOldestArticle = conn.prepareStatement(
270 "SELECT message_id FROM article_ids WHERE article_id = "
271 + "(SELECT Min(article_id) FROM article_ids)");
273 // Prepare statement for method getFirstArticleNumber()
274 this.pstmtGetFirstArticleNumber = conn.prepareStatement(
275 "SELECT Min(article_index) FROM postings WHERE group_id = ?");
277 // Prepare statement for method getPostingsCount()
278 prepareGetPostingsCountStatement();
280 // Prepare statement for method getSubscriptions()
281 prepareGetSubscriptionsStatement();
283 // Prepare statement for method isArticleExisting()
284 this.pstmtIsArticleExisting = conn.prepareStatement(
285 "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
287 // Prepare statement for method isGroupExisting()
288 this.pstmtIsGroupExisting = conn.prepareStatement(
289 "SELECT * FROM groups WHERE name = ?");
291 // Prepare statement for method setConfigValue()
292 this.pstmtSetConfigValue0 = conn.prepareStatement(
293 "DELETE FROM config WHERE config_key = ?");
294 this.pstmtSetConfigValue1 = conn.prepareStatement(
295 "INSERT INTO config VALUES(?, ?)");
297 // Prepare statements for method purgeGroup()
298 this.pstmtPurgeGroup0 = conn.prepareStatement(
299 "DELETE FROM peer_subscriptions WHERE group_id = ?");
300 this.pstmtPurgeGroup1 = conn.prepareStatement(
301 "DELETE FROM groups WHERE group_id = ?");
303 // Prepare statement for method update(Group)
304 this.pstmtUpdateGroup = conn.prepareStatement(
305 "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
306 } catch (ClassNotFoundException ex) {
307 throw new Error("JDBC Driver not found!", ex);
312 * Adds an article to the database.
315 * @throws java.sql.SQLException
318 public void addArticle(final Article article)
319 throws StorageBackendException
322 this.conn.setAutoCommit(false);
324 int newArticleID = getMaxArticleID() + 1;
325 addArticle(article, newArticleID);
327 this.conn.setAutoCommit(true);
329 this.restarts = 0; // Reset error count
330 } catch (SQLException ex) {
332 this.conn.rollback(); // Rollback changes
333 } catch (SQLException ex2) {
334 Log.get().severe("Rollback of addArticle() failed: " + ex2);
338 this.conn.setAutoCommit(true); // and release locks
339 } catch (SQLException ex2) {
340 Log.get().severe("setAutoCommit(true) of addArticle() failed: " + ex2);
343 restartConnection(ex);
349 * Adds an article to the database.
352 * @throws java.sql.SQLException
354 void addArticle(final Article article, final int newArticleID)
355 throws SQLException, StorageBackendException
357 // Fill prepared statement with values;
358 // writes body to article table
359 pstmtAddArticle1.setInt(1, newArticleID);
360 pstmtAddArticle1.setBytes(2, article.getBody());
361 pstmtAddArticle1.execute();
364 Enumeration headers = article.getAllHeaders();
365 for (int n = 0; headers.hasMoreElements(); n++) {
366 Header header = (Header) headers.nextElement();
367 pstmtAddArticle2.setInt(1, newArticleID);
368 pstmtAddArticle2.setString(2, header.getName().toLowerCase());
369 pstmtAddArticle2.setString(3,
370 header.getValue().replaceAll("[\r\n]", ""));
371 pstmtAddArticle2.setInt(4, n);
372 pstmtAddArticle2.execute();
375 // For each newsgroup add a reference
376 List<Group> groups = article.getGroups();
377 for (Group group : groups) {
378 pstmtAddArticle3.setLong(1, group.getInternalID());
379 pstmtAddArticle3.setInt(2, newArticleID);
380 pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
381 pstmtAddArticle3.execute();
384 // Write message-id to article_ids table
385 this.pstmtAddArticle4.setInt(1, newArticleID);
386 this.pstmtAddArticle4.setString(2, article.getMessageID());
387 this.pstmtAddArticle4.execute();
391 * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
393 * @throws java.sql.SQLException
396 public void addGroup(String name, int flags)
397 throws StorageBackendException
400 this.conn.setAutoCommit(false);
401 pstmtAddGroup0.setString(1, name);
402 pstmtAddGroup0.setInt(2, flags);
404 pstmtAddGroup0.executeUpdate();
406 this.conn.setAutoCommit(true);
407 this.restarts = 0; // Reset error count
408 } catch (SQLException ex) {
410 this.conn.rollback();
411 this.conn.setAutoCommit(true);
412 } catch (SQLException ex2) {
413 ex2.printStackTrace();
416 restartConnection(ex);
417 addGroup(name, flags);
422 public void addEvent(long time, int type, long gid)
423 throws StorageBackendException
426 this.conn.setAutoCommit(false);
427 this.pstmtAddEvent.setLong(1, time);
428 this.pstmtAddEvent.setInt(2, type);
429 this.pstmtAddEvent.setLong(3, gid);
430 this.pstmtAddEvent.executeUpdate();
432 this.conn.setAutoCommit(true);
434 } catch (SQLException ex) {
436 this.conn.rollback();
437 this.conn.setAutoCommit(true);
438 } catch (SQLException ex2) {
439 ex2.printStackTrace();
442 restartConnection(ex);
443 addEvent(time, type, gid);
448 public int countArticles()
449 throws StorageBackendException
454 rs = this.pstmtCountArticles.executeQuery();
460 } catch (SQLException ex) {
461 restartConnection(ex);
462 return countArticles();
467 } catch (SQLException ex) {
468 ex.printStackTrace();
476 public int countGroups()
477 throws StorageBackendException
482 rs = this.pstmtCountGroups.executeQuery();
488 } catch (SQLException ex) {
489 restartConnection(ex);
490 return countGroups();
495 } catch (SQLException ex) {
496 ex.printStackTrace();
504 public void delete(final String messageID)
505 throws StorageBackendException
508 this.conn.setAutoCommit(false);
510 this.pstmtDeleteArticle0.setString(1, messageID);
511 int rs = this.pstmtDeleteArticle0.executeUpdate();
513 // We do not trust the ON DELETE CASCADE functionality to delete
514 // orphaned references...
515 this.pstmtDeleteArticle1.setString(1, messageID);
516 rs = this.pstmtDeleteArticle1.executeUpdate();
518 this.pstmtDeleteArticle2.setString(1, messageID);
519 rs = this.pstmtDeleteArticle2.executeUpdate();
521 this.pstmtDeleteArticle3.setString(1, messageID);
522 rs = this.pstmtDeleteArticle3.executeUpdate();
525 this.conn.setAutoCommit(true);
526 } catch (SQLException ex) {
527 throw new StorageBackendException(ex);
532 public Article getArticle(String messageID)
533 throws StorageBackendException
537 pstmtGetArticle0.setString(1, messageID);
538 rs = pstmtGetArticle0.executeQuery();
543 byte[] body = rs.getBytes("body");
544 String headers = getArticleHeaders(rs.getInt("article_id"));
545 return new Article(headers, body);
547 } catch (SQLException ex) {
548 restartConnection(ex);
549 return getArticle(messageID);
554 } catch (SQLException ex) {
555 ex.printStackTrace();
557 restarts = 0; // Reset error count
563 * Retrieves an article by its ID.
566 * @throws StorageBackendException
569 public Article getArticle(long articleIndex, long gid)
570 throws StorageBackendException
575 this.pstmtGetArticle1.setLong(1, articleIndex);
576 this.pstmtGetArticle1.setLong(2, gid);
578 rs = this.pstmtGetArticle1.executeQuery();
581 byte[] body = rs.getBytes("body");
582 String headers = getArticleHeaders(rs.getInt("article_id"));
583 return new Article(headers, body);
587 } catch (SQLException ex) {
588 restartConnection(ex);
589 return getArticle(articleIndex, gid);
594 } catch (SQLException ex) {
595 ex.printStackTrace();
603 * Searches for fitting header values using the given regular expression.
610 * @throws StorageBackendException
613 public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
614 long end, String headerKey, String patStr)
615 throws StorageBackendException, PatternSyntaxException
618 List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
621 this.pstmtGetArticleHeaders1.setString(1, group.getName());
622 this.pstmtGetArticleHeaders1.setString(2, headerKey);
623 this.pstmtGetArticleHeaders1.setLong(3, start);
625 rs = this.pstmtGetArticleHeaders1.executeQuery();
627 // Convert the "NNTP" regex to Java regex
628 patStr = patStr.replace("*", ".*");
629 Pattern pattern = Pattern.compile(patStr);
632 Long articleIndex = rs.getLong(1);
633 if (end < 0 || articleIndex <= end) // Match start is done via SQL
635 String headerValue = rs.getString(2);
636 Matcher matcher = pattern.matcher(headerValue);
637 if (matcher.matches()) {
638 heads.add(new Pair<Long, String>(articleIndex, headerValue));
642 } catch (SQLException ex) {
643 restartConnection(ex);
644 return getArticleHeaders(group, start, end, headerKey, patStr);
649 } catch (SQLException ex) {
650 ex.printStackTrace();
658 private String getArticleHeaders(long articleID)
659 throws StorageBackendException
664 this.pstmtGetArticleHeaders0.setLong(1, articleID);
665 rs = this.pstmtGetArticleHeaders0.executeQuery();
667 StringBuilder buf = new StringBuilder();
670 buf.append(rs.getString(1)); // key
672 String foldedValue = MimeUtility.fold(0, rs.getString(2));
673 buf.append(foldedValue); // value
682 return buf.toString();
683 } catch (SQLException ex) {
684 restartConnection(ex);
685 return getArticleHeaders(articleID);
690 } catch (SQLException ex) {
691 ex.printStackTrace();
698 public long getArticleIndex(Article article, Group group)
699 throws StorageBackendException
704 this.pstmtGetArticleIndex.setString(1, article.getMessageID());
705 this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
707 rs = this.pstmtGetArticleIndex.executeQuery();
709 return rs.getLong(1);
713 } catch (SQLException ex) {
714 restartConnection(ex);
715 return getArticleIndex(article, group);
720 } catch (SQLException ex) {
721 ex.printStackTrace();
728 * Returns a list of Long/Article Pairs.
729 * @throws java.sql.SQLException
732 public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
734 throws StorageBackendException
739 this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
740 this.pstmtGetArticleHeads.setLong(2, first);
741 this.pstmtGetArticleHeads.setLong(3, last);
742 rs = pstmtGetArticleHeads.executeQuery();
744 List<Pair<Long, ArticleHead>> articles = new ArrayList<Pair<Long, ArticleHead>>();
747 long aid = rs.getLong("article_id");
748 long aidx = rs.getLong("article_index");
749 String headers = getArticleHeaders(aid);
750 articles.add(new Pair<Long, ArticleHead>(aidx,
751 new ArticleHead(headers)));
755 } catch (SQLException ex) {
756 restartConnection(ex);
757 return getArticleHeads(group, first, last);
762 } catch (SQLException ex) {
763 ex.printStackTrace();
770 public List<Long> getArticleNumbers(long gid)
771 throws StorageBackendException
775 List<Long> ids = new ArrayList<Long>();
776 this.pstmtGetArticleIDs.setLong(1, gid);
777 rs = this.pstmtGetArticleIDs.executeQuery();
779 ids.add(rs.getLong(1));
782 } catch (SQLException ex) {
783 restartConnection(ex);
784 return getArticleNumbers(gid);
789 restarts = 0; // Clear the restart count after successful request
790 } catch (SQLException ex) {
791 ex.printStackTrace();
798 public String getConfigValue(String key)
799 throws StorageBackendException
803 this.pstmtGetConfigValue.setString(1, key);
805 rs = this.pstmtGetConfigValue.executeQuery();
807 return rs.getString(1); // First data on index 1 not 0
811 } catch (SQLException ex) {
812 restartConnection(ex);
813 return getConfigValue(key);
818 } catch (SQLException ex) {
819 ex.printStackTrace();
821 restarts = 0; // Clear the restart count after successful request
827 public int getEventsCount(int type, long start, long end, Channel channel)
828 throws StorageBackendException
833 if (channel == null) {
834 this.pstmtGetEventsCount0.setInt(1, type);
835 this.pstmtGetEventsCount0.setLong(2, start);
836 this.pstmtGetEventsCount0.setLong(3, end);
837 rs = this.pstmtGetEventsCount0.executeQuery();
839 this.pstmtGetEventsCount1.setInt(1, type);
840 this.pstmtGetEventsCount1.setLong(2, start);
841 this.pstmtGetEventsCount1.setLong(3, end);
842 this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
843 rs = this.pstmtGetEventsCount1.executeQuery();
851 } catch (SQLException ex) {
852 restartConnection(ex);
853 return getEventsCount(type, start, end, channel);
858 } catch (SQLException ex) {
859 ex.printStackTrace();
866 * Reads all Groups from the JDBCDatabase.
868 * @throws StorageBackendException
871 public List<Channel> getGroups()
872 throws StorageBackendException
875 List<Channel> buffer = new ArrayList<Channel>();
876 Statement stmt = null;
879 stmt = conn.createStatement();
880 rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
883 String name = rs.getString("name");
884 long id = rs.getLong("group_id");
885 int flags = rs.getInt("flags");
887 Group group = new Group(name, id, flags);
892 } catch (SQLException ex) {
893 restartConnection(ex);
898 stmt.close(); // Implicitely closes ResultSets
899 } catch (SQLException ex) {
900 ex.printStackTrace();
907 public List<String> getGroupsForList(String listAddress)
908 throws StorageBackendException
913 this.pstmtGetGroupForList.setString(1, listAddress);
915 rs = this.pstmtGetGroupForList.executeQuery();
916 List<String> groups = new ArrayList<String>();
918 String group = rs.getString(1);
922 } catch (SQLException ex) {
923 restartConnection(ex);
924 return getGroupsForList(listAddress);
929 } catch (SQLException ex) {
930 ex.printStackTrace();
937 * Returns the Group that is identified by the name.
940 * @throws StorageBackendException
943 public Group getGroup(String name)
944 throws StorageBackendException
949 this.pstmtGetGroup0.setString(1, name);
950 rs = this.pstmtGetGroup0.executeQuery();
955 long id = rs.getLong("group_id");
956 int flags = rs.getInt("flags");
957 return new Group(name, id, flags);
959 } catch (SQLException ex) {
960 restartConnection(ex);
961 return getGroup(name);
966 } catch (SQLException ex) {
967 ex.printStackTrace();
974 public List<String> getListsForGroup(String group)
975 throws StorageBackendException
978 List<String> lists = new ArrayList<String>();
981 this.pstmtGetListForGroup.setString(1, group);
982 rs = this.pstmtGetListForGroup.executeQuery();
985 lists.add(rs.getString(1));
988 } catch (SQLException ex) {
989 restartConnection(ex);
990 return getListsForGroup(group);
995 } catch (SQLException ex) {
996 ex.printStackTrace();
1002 private int getMaxArticleIndex(long groupID)
1003 throws StorageBackendException
1005 ResultSet rs = null;
1008 this.pstmtGetMaxArticleIndex.setLong(1, groupID);
1009 rs = this.pstmtGetMaxArticleIndex.executeQuery();
1013 maxIndex = rs.getInt(1);
1017 } catch (SQLException ex) {
1018 restartConnection(ex);
1019 return getMaxArticleIndex(groupID);
1024 } catch (SQLException ex) {
1025 ex.printStackTrace();
1031 private int getMaxArticleID()
1032 throws StorageBackendException
1034 ResultSet rs = null;
1037 rs = this.pstmtGetMaxArticleID.executeQuery();
1041 maxIndex = rs.getInt(1);
1045 } catch (SQLException ex) {
1046 restartConnection(ex);
1047 return getMaxArticleID();
1052 } catch (SQLException ex) {
1053 ex.printStackTrace();
1060 public int getLastArticleNumber(Group group)
1061 throws StorageBackendException
1063 ResultSet rs = null;
1066 this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
1067 rs = this.pstmtGetLastArticleNumber.executeQuery();
1069 return rs.getInt(1);
1073 } catch (SQLException ex) {
1074 restartConnection(ex);
1075 return getLastArticleNumber(group);
1080 } catch (SQLException ex) {
1081 ex.printStackTrace();
1088 public int getFirstArticleNumber(Group group)
1089 throws StorageBackendException
1091 ResultSet rs = null;
1093 this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
1094 rs = this.pstmtGetFirstArticleNumber.executeQuery();
1096 return rs.getInt(1);
1100 } catch (SQLException ex) {
1101 restartConnection(ex);
1102 return getFirstArticleNumber(group);
1107 } catch (SQLException ex) {
1108 ex.printStackTrace();
1115 * Returns a group name identified by the given id.
1118 * @throws StorageBackendException
1120 public String getGroup(int id)
1121 throws StorageBackendException
1123 ResultSet rs = null;
1126 this.pstmtGetGroup1.setInt(1, id);
1127 rs = this.pstmtGetGroup1.executeQuery();
1130 return rs.getString(1);
1134 } catch (SQLException ex) {
1135 restartConnection(ex);
1136 return getGroup(id);
1141 } catch (SQLException ex) {
1142 ex.printStackTrace();
1149 public double getEventsPerHour(int key, long gid)
1150 throws StorageBackendException
1152 String gidquery = "";
1154 gidquery = " AND group_id = " + gid;
1157 Statement stmt = null;
1158 ResultSet rs = null;
1161 stmt = this.conn.createStatement();
1162 rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))"
1163 + " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
1166 restarts = 0; // reset error count
1167 return rs.getDouble(1);
1171 } catch (SQLException ex) {
1172 restartConnection(ex);
1173 return getEventsPerHour(key, gid);
1177 stmt.close(); // Implicitely closes the result sets
1179 } catch (SQLException ex) {
1180 ex.printStackTrace();
1186 public String getOldestArticle()
1187 throws StorageBackendException
1189 ResultSet rs = null;
1192 rs = this.pstmtGetOldestArticle.executeQuery();
1194 return rs.getString(1);
1198 } catch (SQLException ex) {
1199 restartConnection(ex);
1200 return getOldestArticle();
1205 } catch (SQLException ex) {
1206 ex.printStackTrace();
1213 public int getPostingsCount(String groupname)
1214 throws StorageBackendException
1216 ResultSet rs = null;
1219 this.pstmtGetPostingsCount.setString(1, groupname);
1220 rs = this.pstmtGetPostingsCount.executeQuery();
1222 return rs.getInt(1);
1224 Log.get().warning("Count on postings return nothing!");
1227 } catch (SQLException ex) {
1228 restartConnection(ex);
1229 return getPostingsCount(groupname);
1234 } catch (SQLException ex) {
1235 ex.printStackTrace();
1242 public List<Subscription> getSubscriptions(int feedtype)
1243 throws StorageBackendException
1245 ResultSet rs = null;
1248 List<Subscription> subs = new ArrayList<Subscription>();
1249 this.pstmtGetSubscriptions.setInt(1, feedtype);
1250 rs = this.pstmtGetSubscriptions.executeQuery();
1253 String host = rs.getString("host");
1254 String group = rs.getString("name");
1255 int port = rs.getInt("port");
1256 subs.add(new Subscription(host, port, feedtype, group));
1260 } catch (SQLException ex) {
1261 restartConnection(ex);
1262 return getSubscriptions(feedtype);
1267 } catch (SQLException ex) {
1268 ex.printStackTrace();
1275 * Checks if there is an article with the given messageid in the JDBCDatabase.
1278 * @throws StorageBackendException
1281 public boolean isArticleExisting(String messageID)
1282 throws StorageBackendException
1284 ResultSet rs = null;
1287 this.pstmtIsArticleExisting.setString(1, messageID);
1288 rs = this.pstmtIsArticleExisting.executeQuery();
1289 return rs.next() && rs.getInt(1) == 1;
1290 } catch (SQLException ex) {
1291 restartConnection(ex);
1292 return isArticleExisting(messageID);
1297 } catch (SQLException ex) {
1298 ex.printStackTrace();
1305 * Checks if there is a group with the given name in the JDBCDatabase.
1308 * @throws StorageBackendException
1311 public boolean isGroupExisting(String name)
1312 throws StorageBackendException
1314 ResultSet rs = null;
1317 this.pstmtIsGroupExisting.setString(1, name);
1318 rs = this.pstmtIsGroupExisting.executeQuery();
1320 } catch (SQLException ex) {
1321 restartConnection(ex);
1322 return isGroupExisting(name);
1327 } catch (SQLException ex) {
1328 ex.printStackTrace();
1335 public void setConfigValue(String key, String value)
1336 throws StorageBackendException
1339 conn.setAutoCommit(false);
1340 this.pstmtSetConfigValue0.setString(1, key);
1341 this.pstmtSetConfigValue0.execute();
1342 this.pstmtSetConfigValue1.setString(1, key);
1343 this.pstmtSetConfigValue1.setString(2, value);
1344 this.pstmtSetConfigValue1.execute();
1346 conn.setAutoCommit(true);
1347 } catch (SQLException ex) {
1348 restartConnection(ex);
1349 setConfigValue(key, value);
1354 * Closes the JDBCDatabase connection.
1356 public void shutdown()
1357 throws StorageBackendException
1360 if (this.conn != null) {
1363 } catch (SQLException ex) {
1364 throw new StorageBackendException(ex);
1369 public void purgeGroup(Group group)
1370 throws StorageBackendException
1373 this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
1374 this.pstmtPurgeGroup0.executeUpdate();
1376 this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
1377 this.pstmtPurgeGroup1.executeUpdate();
1378 } catch (SQLException ex) {
1379 restartConnection(ex);
1384 protected void restartConnection(SQLException cause)
1385 throws StorageBackendException
1388 Log.get().log(Level.SEVERE, Thread.currentThread()
1389 + ": Database connection was closed (restart " + restarts + ").", cause);
1391 if (restarts >= MAX_RESTARTS) {
1392 // Delete the current, probably broken JDBCDatabase instance.
1393 // So no one can use the instance any more.
1394 JDBCDatabaseProvider.instances.remove(Thread.currentThread());
1396 // Throw the exception upwards
1397 throw new StorageBackendException(cause);
1401 Thread.sleep(1500L * restarts);
1402 } catch (InterruptedException ex) {
1403 Log.get().warning("Interrupted: " + ex.getMessage());
1406 // Try to properly close the old database connection
1408 if (this.conn != null) {
1411 } catch (SQLException ex) {
1412 Log.get().warning(ex.getMessage());
1416 // Try to reinitialize database connection
1418 } catch (SQLException ex) {
1419 Log.get().warning(ex.getMessage());
1420 restartConnection(ex);
1425 public boolean update(Article article)
1426 throws StorageBackendException
1428 ResultSet rs = null;
1430 // Retrieve internal article_id
1431 this.pstmtGetArticle0.setString(1, article.getMessageID());
1432 rs = this.pstmtGetArticle0.executeQuery();
1433 int articleID = rs.getInt("article_id");
1435 delete(article.getMessageID());
1437 this.conn.setAutoCommit(false);
1438 addArticle(article, articleID);
1440 this.conn.setAutoCommit(true);
1442 } catch (SQLException ex) {
1444 this.conn.rollback();
1445 } catch(SQLException ex2) {
1446 Log.get().severe("Rollback failed: " + ex2.getMessage());
1448 restartConnection(ex);
1449 return update(article);
1454 * Writes the flags and the name of the given group to the database.
1456 * @throws StorageBackendException
1459 public boolean update(Group group)
1460 throws StorageBackendException
1463 this.pstmtUpdateGroup.setInt(1, group.getFlags());
1464 this.pstmtUpdateGroup.setString(2, group.getName());
1465 this.pstmtUpdateGroup.setLong(3, group.getInternalID());
1466 int rs = this.pstmtUpdateGroup.executeUpdate();
1468 } catch (SQLException ex) {
1469 restartConnection(ex);
1470 return update(group);