org/sonews/storage/impl/JDBCDatabase.java
author cli
Thu, 20 Aug 2009 16:49:38 +0200
changeset 13 de98fd5b35f5
parent 12 bb6990c0dd1a
child 14 efce4ec25564
permissions -rw-r--r--
Remove aggregated group stubs and fix #543.
     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 
    19 package org.sonews.storage.impl;
    20 
    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.regex.Matcher;
    31 import java.util.regex.Pattern;
    32 import java.util.regex.PatternSyntaxException;
    33 import javax.mail.Header;
    34 import javax.mail.internet.InternetAddress;
    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;
    46 
    47 /**
    48  * JDBCDatabase facade class.
    49  * @author Christian Lins
    50  * @since sonews/0.5.0
    51  */
    52 // TODO: Refactor this class to reduce size (e.g. ArticleDatabase GroupDatabase)
    53 public class JDBCDatabase implements Storage
    54 {
    55 
    56   public static final int MAX_RESTARTS = 3;
    57   
    58   private Connection        conn = null;
    59   private PreparedStatement pstmtAddArticle1 = null;
    60   private PreparedStatement pstmtAddArticle2 = null;
    61   private PreparedStatement pstmtAddArticle3 = null;
    62   private PreparedStatement pstmtAddArticle4 = null;
    63   private PreparedStatement pstmtAddGroup0   = null;
    64   private PreparedStatement pstmtAddEvent = null;
    65   private PreparedStatement pstmtCountArticles = null;
    66   private PreparedStatement pstmtCountGroups   = null;
    67   private PreparedStatement pstmtDeleteArticle0 = null;
    68   private PreparedStatement pstmtDeleteArticle1 = null;
    69   private PreparedStatement pstmtDeleteArticle2 = null;
    70   private PreparedStatement pstmtDeleteArticle3 = null;
    71   private PreparedStatement pstmtGetArticle0 = null;
    72   private PreparedStatement pstmtGetArticle1 = null;
    73   private PreparedStatement pstmtGetArticleHeaders0 = null;
    74   private PreparedStatement pstmtGetArticleHeaders1 = null;
    75   private PreparedStatement pstmtGetArticleHeads = null;
    76   private PreparedStatement pstmtGetArticleIDs   = null;
    77   private PreparedStatement pstmtGetArticleIndex    = null;
    78   private PreparedStatement pstmtGetConfigValue = null;
    79   private PreparedStatement pstmtGetEventsCount0 = null;
    80   private PreparedStatement pstmtGetEventsCount1 = null;
    81   private PreparedStatement pstmtGetGroupForList = null;
    82   private PreparedStatement pstmtGetGroup0     = null;
    83   private PreparedStatement pstmtGetGroup1     = null;
    84   private PreparedStatement pstmtGetFirstArticleNumber = null;
    85   private PreparedStatement pstmtGetListForGroup       = null;
    86   private PreparedStatement pstmtGetLastArticleNumber  = null;
    87   private PreparedStatement pstmtGetMaxArticleID       = null;
    88   private PreparedStatement pstmtGetMaxArticleIndex    = null;
    89   private PreparedStatement pstmtGetOldestArticle      = null;
    90   private PreparedStatement pstmtGetPostingsCount      = null;
    91   private PreparedStatement pstmtGetSubscriptions  = null;
    92   private PreparedStatement pstmtIsArticleExisting = null;
    93   private PreparedStatement pstmtIsGroupExisting = null;
    94   private PreparedStatement pstmtPurgeGroup0     = null;
    95   private PreparedStatement pstmtPurgeGroup1     = null;
    96   private PreparedStatement pstmtSetConfigValue0 = null;
    97   private PreparedStatement pstmtSetConfigValue1 = null;
    98   private PreparedStatement pstmtUpdateGroup     = null;
    99   
   100   /** How many times the database connection was reinitialized */
   101   private int restarts = 0;
   102   
   103   /**
   104    * Rises the database: reconnect and recreate all prepared statements.
   105    * @throws java.lang.SQLException
   106    */
   107   protected void arise()
   108     throws SQLException
   109   {
   110     try
   111     {
   112       // Load database driver
   113       Class.forName(
   114               Config.inst().get(Config.STORAGE_DBMSDRIVER, "java.lang.Object"));
   115 
   116       // Establish database connection
   117       this.conn = DriverManager.getConnection(
   118               Config.inst().get(Config.STORAGE_DATABASE, "<not specified>"),
   119               Config.inst().get(Config.STORAGE_USER, "root"),
   120               Config.inst().get(Config.STORAGE_PASSWORD, ""));
   121 
   122       this.conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
   123       if(this.conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE)
   124       {
   125         Log.msg("Warning: Database is NOT fully serializable!", false);
   126       }
   127 
   128       // Prepare statements for method addArticle()
   129       this.pstmtAddArticle1 = conn.prepareStatement(
   130         "INSERT INTO articles (article_id, body) VALUES(?, ?)");
   131       this.pstmtAddArticle2 = conn.prepareStatement(
   132         "INSERT INTO headers (article_id, header_key, header_value, header_index) " +
   133         "VALUES (?, ?, ?, ?)");
   134       this.pstmtAddArticle3 = conn.prepareStatement(
   135         "INSERT INTO postings (group_id, article_id, article_index)" +
   136         "VALUES (?, ?, ?)");
   137       this.pstmtAddArticle4 = conn.prepareStatement(
   138         "INSERT INTO article_ids (article_id, message_id) VALUES (?, ?)");
   139 
   140       // Prepare statement for method addStatValue()
   141       this.pstmtAddEvent = conn.prepareStatement(
   142         "INSERT INTO events VALUES (?, ?, ?)");
   143      
   144       // Prepare statement for method addGroup()
   145       this.pstmtAddGroup0 = conn.prepareStatement(
   146         "INSERT INTO groups (name, flags) VALUES (?, ?)");
   147       
   148       // Prepare statement for method countArticles()
   149       this.pstmtCountArticles = conn.prepareStatement(
   150         "SELECT Count(article_id) FROM article_ids");
   151       
   152       // Prepare statement for method countGroups()
   153       this.pstmtCountGroups = conn.prepareStatement(
   154         "SELECT Count(group_id) FROM groups WHERE " +
   155         "flags & " + Channel.DELETED + " = 0");
   156       
   157       // Prepare statements for method delete(article)
   158       this.pstmtDeleteArticle0 = conn.prepareStatement(
   159         "DELETE FROM articles WHERE article_id = " +
   160         "(SELECT article_id FROM article_ids WHERE message_id = ?)");
   161       this.pstmtDeleteArticle1 = conn.prepareStatement(
   162         "DELETE FROM headers WHERE article_id = " +
   163         "(SELECT article_id FROM article_ids WHERE message_id = ?)");
   164       this.pstmtDeleteArticle2 = conn.prepareStatement(
   165         "DELETE FROM postings WHERE article_id = " +
   166         "(SELECT article_id FROM article_ids WHERE message_id = ?)");
   167       this.pstmtDeleteArticle3 = conn.prepareStatement(
   168         "DELETE FROM article_ids WHERE message_id = ?");
   169 
   170       // Prepare statements for methods getArticle()
   171       this.pstmtGetArticle0 = conn.prepareStatement(
   172         "SELECT * FROM articles  WHERE article_id = " +
   173         "(SELECT article_id FROM article_ids WHERE message_id = ?)");
   174       this.pstmtGetArticle1 = conn.prepareStatement(
   175         "SELECT * FROM articles WHERE article_id = " +
   176         "(SELECT article_id FROM postings WHERE " +
   177         "article_index = ? AND group_id = ?)");
   178       
   179       // Prepare statement for method getArticleHeaders()
   180       this.pstmtGetArticleHeaders0 = conn.prepareStatement(
   181         "SELECT header_key, header_value FROM headers WHERE article_id = ? " +
   182         "ORDER BY header_index ASC");
   183 
   184       // Prepare statement for method getArticleHeaders(regular expr pattern)
   185       this.pstmtGetArticleHeaders1 = conn.prepareStatement(
   186         "SELECT p.article_index, h.header_value FROM headers h " +
   187           "INNER JOIN postings p ON h.article_id = p.article_id " +
   188           "INNER JOIN groups g ON p.group_id = g.group_id " +
   189             "WHERE g.name          =  ? AND " +
   190                   "h.header_key    =  ? AND " +
   191                   "p.article_index >= ? " +
   192         "ORDER BY p.article_index ASC");
   193 
   194       this.pstmtGetArticleIDs = conn.prepareStatement(
   195         "SELECT article_index FROM postings WHERE group_id = ?");
   196       
   197       // Prepare statement for method getArticleIndex
   198       this.pstmtGetArticleIndex = conn.prepareStatement(
   199               "SELECT article_index FROM postings WHERE " +
   200               "article_id = (SELECT article_id FROM article_ids " +
   201               "WHERE message_id = ?) " +
   202               " AND group_id = ?");
   203 
   204       // Prepare statements for method getArticleHeads()
   205       this.pstmtGetArticleHeads = conn.prepareStatement(
   206         "SELECT article_id, article_index FROM postings WHERE " +
   207         "postings.group_id = ? AND article_index >= ? AND " +
   208         "article_index <= ?");
   209 
   210       // Prepare statements for method getConfigValue()
   211       this.pstmtGetConfigValue = conn.prepareStatement(
   212         "SELECT config_value FROM config WHERE config_key = ?");
   213 
   214       // Prepare statements for method getEventsCount()
   215       this.pstmtGetEventsCount0 = conn.prepareStatement(
   216         "SELECT Count(*) FROM events WHERE event_key = ? AND " +
   217         "event_time >= ? AND event_time < ?");
   218 
   219       this.pstmtGetEventsCount1 = conn.prepareStatement(
   220         "SELECT Count(*) FROM events WHERE event_key = ? AND " +
   221         "event_time >= ? AND event_time < ? AND group_id = ?");
   222       
   223       // Prepare statement for method getGroupForList()
   224       this.pstmtGetGroupForList = conn.prepareStatement(
   225         "SELECT name FROM groups INNER JOIN groups2list " +
   226         "ON groups.group_id = groups2list.group_id " +
   227         "WHERE groups2list.listaddress = ?");
   228 
   229       // Prepare statement for method getGroup()
   230       this.pstmtGetGroup0 = conn.prepareStatement(
   231         "SELECT group_id, flags FROM groups WHERE Name = ?");
   232       this.pstmtGetGroup1 = conn.prepareStatement(
   233         "SELECT name FROM groups WHERE group_id = ?");
   234 
   235       // Prepare statement for method getLastArticleNumber()
   236       this.pstmtGetLastArticleNumber = conn.prepareStatement(
   237         "SELECT Max(article_index) FROM postings WHERE group_id = ?");
   238 
   239       // Prepare statement for method getListForGroup()
   240       this.pstmtGetListForGroup = conn.prepareStatement(
   241         "SELECT listaddress FROM groups2list INNER JOIN groups " +
   242         "ON groups.group_id = groups2list.group_id WHERE name = ?");
   243 
   244       // Prepare statement for method getMaxArticleID()
   245       this.pstmtGetMaxArticleID = conn.prepareStatement(
   246         "SELECT Max(article_id) FROM articles");
   247       
   248       // Prepare statement for method getMaxArticleIndex()
   249       this.pstmtGetMaxArticleIndex = conn.prepareStatement(
   250         "SELECT Max(article_index) FROM postings WHERE group_id = ?");
   251       
   252       // Prepare statement for method getOldestArticle()
   253       this.pstmtGetOldestArticle = conn.prepareStatement(
   254         "SELECT message_id FROM article_ids WHERE article_id = " +
   255         "(SELECT Min(article_id) FROM article_ids)");
   256 
   257       // Prepare statement for method getFirstArticleNumber()
   258       this.pstmtGetFirstArticleNumber = conn.prepareStatement(
   259         "SELECT Min(article_index) FROM postings WHERE group_id = ?");
   260       
   261       // Prepare statement for method getPostingsCount()
   262       this.pstmtGetPostingsCount = conn.prepareStatement(
   263         "SELECT Count(*) FROM postings NATURAL JOIN groups " +
   264         "WHERE groups.name = ?");
   265       
   266       // Prepare statement for method getSubscriptions()
   267       this.pstmtGetSubscriptions = conn.prepareStatement(
   268         "SELECT host, port, name FROM peers NATURAL JOIN " +
   269         "peer_subscriptions NATURAL JOIN groups WHERE feedtype = ?");
   270       
   271       // Prepare statement for method isArticleExisting()
   272       this.pstmtIsArticleExisting = conn.prepareStatement(
   273         "SELECT Count(article_id) FROM article_ids WHERE message_id = ?");
   274       
   275       // Prepare statement for method isGroupExisting()
   276       this.pstmtIsGroupExisting = conn.prepareStatement(
   277         "SELECT * FROM groups WHERE name = ?");
   278       
   279       // Prepare statement for method setConfigValue()
   280       this.pstmtSetConfigValue0 = conn.prepareStatement(
   281         "DELETE FROM config WHERE config_key = ?");
   282       this.pstmtSetConfigValue1 = conn.prepareStatement(
   283         "INSERT INTO config VALUES(?, ?)");
   284 
   285       // Prepare statements for method purgeGroup()
   286       this.pstmtPurgeGroup0 = conn.prepareStatement(
   287         "DELETE FROM peer_subscriptions WHERE group_id = ?");
   288       this.pstmtPurgeGroup1 = conn.prepareStatement(
   289         "DELETE FROM groups WHERE group_id = ?");
   290 
   291       // Prepare statement for method update(Group)
   292       this.pstmtUpdateGroup = conn.prepareStatement(
   293         "UPDATE groups SET flags = ?, name = ? WHERE group_id = ?");
   294     }
   295     catch(ClassNotFoundException ex)
   296     {
   297       throw new Error("JDBC Driver not found!", ex);
   298     }
   299   }
   300   
   301   /**
   302    * Adds an article to the database.
   303    * @param article
   304    * @return
   305    * @throws java.sql.SQLException
   306    */
   307   @Override
   308   public void addArticle(final Article article)
   309     throws StorageBackendException
   310   {
   311     try
   312     {
   313       this.conn.setAutoCommit(false);
   314 
   315       int newArticleID = getMaxArticleID() + 1;
   316 
   317       // Fill prepared statement with values;
   318       // writes body to article table
   319       pstmtAddArticle1.setInt(1, newArticleID);
   320       pstmtAddArticle1.setBytes(2, article.getBody());
   321       pstmtAddArticle1.execute();
   322 
   323       // Add headers
   324       Enumeration headers = article.getAllHeaders();
   325       for(int n = 0; headers.hasMoreElements(); n++)
   326       {
   327         Header header = (Header)headers.nextElement();
   328         pstmtAddArticle2.setInt(1, newArticleID);
   329         pstmtAddArticle2.setString(2, header.getName().toLowerCase());
   330         pstmtAddArticle2.setString(3, 
   331           header.getValue().replaceAll("[\r\n]", ""));
   332         pstmtAddArticle2.setInt(4, n);
   333         pstmtAddArticle2.execute();
   334       }
   335       
   336       // For each newsgroup add a reference
   337       List<Group> groups = article.getGroups();
   338       for(Group group : groups)
   339       {
   340         pstmtAddArticle3.setLong(1, group.getInternalID());
   341         pstmtAddArticle3.setInt(2, newArticleID);
   342         pstmtAddArticle3.setLong(3, getMaxArticleIndex(group.getInternalID()) + 1);
   343         pstmtAddArticle3.execute();
   344       }
   345       
   346       // Write message-id to article_ids table
   347       this.pstmtAddArticle4.setInt(1, newArticleID);
   348       this.pstmtAddArticle4.setString(2, article.getMessageID());
   349       this.pstmtAddArticle4.execute();
   350 
   351       this.conn.commit();
   352       this.conn.setAutoCommit(true);
   353 
   354       this.restarts = 0; // Reset error count
   355     }
   356     catch(SQLException ex)
   357     {
   358       try
   359       {
   360         this.conn.rollback();  // Rollback changes
   361       }
   362       catch(SQLException ex2)
   363       {
   364         Log.msg("Rollback of addArticle() failed: " + ex2, false);
   365       }
   366       
   367       try
   368       {
   369         this.conn.setAutoCommit(true); // and release locks
   370       }
   371       catch(SQLException ex2)
   372       {
   373         Log.msg("setAutoCommit(true) of addArticle() failed: " + ex2, false);
   374       }
   375 
   376       restartConnection(ex);
   377       addArticle(article);
   378     }
   379   }
   380   
   381   /**
   382    * Adds a group to the JDBCDatabase. This method is not accessible via NNTP.
   383    * @param name
   384    * @throws java.sql.SQLException
   385    */
   386   @Override
   387   public void addGroup(String name, int flags)
   388     throws StorageBackendException
   389   {
   390     try
   391     {
   392       this.conn.setAutoCommit(false);
   393       pstmtAddGroup0.setString(1, name);
   394       pstmtAddGroup0.setInt(2, flags);
   395 
   396       pstmtAddGroup0.executeUpdate();
   397       this.conn.commit();
   398       this.conn.setAutoCommit(true);
   399       this.restarts = 0; // Reset error count
   400     }
   401     catch(SQLException ex)
   402     {
   403       try
   404       {
   405         this.conn.rollback();
   406         this.conn.setAutoCommit(true);
   407       }
   408       catch(SQLException ex2)
   409       {
   410         ex2.printStackTrace();
   411       }
   412 
   413       restartConnection(ex);
   414       addGroup(name, flags);
   415     }
   416   }
   417 
   418   @Override
   419   public void addEvent(long time, int type, long gid)
   420     throws StorageBackendException
   421   {
   422     try
   423     {
   424       this.conn.setAutoCommit(false);
   425       this.pstmtAddEvent.setLong(1, time);
   426       this.pstmtAddEvent.setInt(2, type);
   427       this.pstmtAddEvent.setLong(3, gid);
   428       this.pstmtAddEvent.executeUpdate();
   429       this.conn.commit();
   430       this.conn.setAutoCommit(true);
   431       this.restarts = 0;
   432     }
   433     catch(SQLException ex)
   434     {
   435       try
   436       {
   437         this.conn.rollback();
   438         this.conn.setAutoCommit(true);
   439       }
   440       catch(SQLException ex2)
   441       {
   442         ex2.printStackTrace();
   443       }
   444 
   445       restartConnection(ex);
   446       addEvent(time, type, gid);
   447     }
   448   }
   449 
   450   @Override
   451   public int countArticles()
   452     throws StorageBackendException
   453   {
   454     ResultSet rs = null;
   455 
   456     try
   457     {
   458       rs = this.pstmtCountArticles.executeQuery();
   459       if(rs.next())
   460       {
   461         return rs.getInt(1);
   462       }
   463       else
   464       {
   465         return -1;
   466       }
   467     }
   468     catch(SQLException ex)
   469     {
   470       restartConnection(ex);
   471       return countArticles();
   472     }
   473     finally
   474     {
   475       if(rs != null)
   476       {
   477         try
   478         {
   479           rs.close();
   480         }
   481         catch(SQLException ex)
   482         {
   483           ex.printStackTrace();
   484         }
   485         restarts = 0;
   486       }
   487     }
   488   }
   489 
   490   @Override
   491   public int countGroups()
   492     throws StorageBackendException
   493   {
   494     ResultSet rs = null;
   495 
   496     try
   497     {
   498       rs = this.pstmtCountGroups.executeQuery();
   499       if(rs.next())
   500       {
   501         return rs.getInt(1);
   502       }
   503       else
   504       {
   505         return -1;
   506       }
   507     }
   508     catch(SQLException ex)
   509     {
   510       restartConnection(ex);
   511       return countGroups();
   512     }
   513     finally
   514     {
   515       if(rs != null)
   516       {
   517         try
   518         {
   519           rs.close();
   520         }
   521         catch(SQLException ex)
   522         {
   523           ex.printStackTrace();
   524         }
   525         restarts = 0;
   526       }
   527     }
   528   }
   529 
   530   @Override
   531   public void delete(final String messageID)
   532     throws StorageBackendException
   533   {
   534     try
   535     {
   536       this.conn.setAutoCommit(false);
   537       
   538       this.pstmtDeleteArticle0.setString(1, messageID);
   539       int rs = this.pstmtDeleteArticle0.executeUpdate();
   540       
   541       // We do not trust the ON DELETE CASCADE functionality to delete
   542       // orphaned references...
   543       this.pstmtDeleteArticle1.setString(1, messageID);
   544       rs = this.pstmtDeleteArticle1.executeUpdate();
   545 
   546       this.pstmtDeleteArticle2.setString(1, messageID);
   547       rs = this.pstmtDeleteArticle2.executeUpdate();
   548 
   549       this.pstmtDeleteArticle3.setString(1, messageID);
   550       rs = this.pstmtDeleteArticle3.executeUpdate();
   551       
   552       this.conn.commit();
   553       this.conn.setAutoCommit(true);
   554     }
   555     catch(SQLException ex)
   556     {
   557       throw new StorageBackendException(ex);
   558     }
   559   }
   560 
   561   @Override
   562   public Article getArticle(String messageID)
   563     throws StorageBackendException
   564   {
   565     ResultSet rs = null;
   566     try
   567     {
   568       pstmtGetArticle0.setString(1, messageID);
   569       rs = pstmtGetArticle0.executeQuery();
   570 
   571       if(!rs.next())
   572       {
   573         return null;
   574       }
   575       else
   576       {
   577         byte[] body     = rs.getBytes("body");
   578         String headers  = getArticleHeaders(rs.getInt("article_id"));
   579         return new Article(headers, body);
   580       }
   581     }
   582     catch(SQLException ex)
   583     {
   584       restartConnection(ex);
   585       return getArticle(messageID);
   586     }
   587     finally
   588     {
   589       if(rs != null)
   590       {
   591         try
   592         {
   593           rs.close();
   594         }
   595         catch(SQLException ex)
   596         {
   597           ex.printStackTrace();
   598         }
   599         restarts = 0; // Reset error count
   600       }
   601     }
   602   }
   603   
   604   /**
   605    * Retrieves an article by its ID.
   606    * @param articleID
   607    * @return
   608    * @throws StorageBackendException
   609    */
   610   @Override
   611   public Article getArticle(long articleIndex, long gid)
   612     throws StorageBackendException
   613   {  
   614     ResultSet rs = null;
   615 
   616     try
   617     {
   618       this.pstmtGetArticle1.setLong(1, articleIndex);
   619       this.pstmtGetArticle1.setLong(2, gid);
   620 
   621       rs = this.pstmtGetArticle1.executeQuery();
   622 
   623       if(rs.next())
   624       {
   625         byte[] body    = rs.getBytes("body");
   626         String headers = getArticleHeaders(rs.getInt("article_id"));
   627         return new Article(headers, body);
   628       }
   629       else
   630       {
   631         return null;
   632       }
   633     }
   634     catch(SQLException ex)
   635     {
   636       restartConnection(ex);
   637       return getArticle(articleIndex, gid);
   638     }
   639     finally
   640     {
   641       if(rs != null)
   642       {
   643         try
   644         {
   645           rs.close();
   646         }
   647         catch(SQLException ex)
   648         {
   649           ex.printStackTrace();
   650         }
   651         restarts = 0;
   652       }
   653     }
   654   }
   655 
   656   /**
   657    * Searches for fitting header values using the given regular expression.
   658    * @param group
   659    * @param start
   660    * @param end
   661    * @param headerKey
   662    * @param pattern
   663    * @return
   664    * @throws StorageBackendException
   665    */
   666   @Override
   667   public List<Pair<Long, String>> getArticleHeaders(Channel group, long start,
   668     long end, String headerKey, String patStr)
   669     throws StorageBackendException, PatternSyntaxException
   670   {
   671     ResultSet rs = null;
   672     List<Pair<Long, String>> heads = new ArrayList<Pair<Long, String>>();
   673 
   674     try
   675     {
   676       this.pstmtGetArticleHeaders1.setString(1, group.getName());
   677       this.pstmtGetArticleHeaders1.setString(2, headerKey);
   678       this.pstmtGetArticleHeaders1.setLong(3, start);
   679 
   680       rs = this.pstmtGetArticleHeaders1.executeQuery();
   681 
   682       // Convert the "NNTP" regex to Java regex
   683       patStr = patStr.replace("*", ".*");
   684       Pattern pattern = Pattern.compile(patStr);
   685 
   686       while(rs.next())
   687       {
   688         Long articleIndex = rs.getLong(1);
   689         if(end < 0 || articleIndex <= end) // Match start is done via SQL
   690         {
   691           String headerValue  = rs.getString(2);
   692           Matcher matcher = pattern.matcher(headerValue);
   693           if(matcher.matches())
   694           {
   695             heads.add(new Pair<Long, String>(articleIndex, headerValue));
   696           }
   697         }
   698       }
   699     }
   700     catch(SQLException ex)
   701     {
   702       restartConnection(ex);
   703       return getArticleHeaders(group, start, end, headerKey, patStr);
   704     }
   705     finally
   706     {
   707       if(rs != null)
   708       {
   709         try
   710         {
   711           rs.close();
   712         }
   713         catch(SQLException ex)
   714         {
   715           ex.printStackTrace();
   716         }
   717       }
   718     }
   719 
   720     return heads;
   721   }
   722 
   723   private String getArticleHeaders(long articleID)
   724     throws StorageBackendException
   725   {
   726     ResultSet rs = null;
   727     
   728     try
   729     {
   730       this.pstmtGetArticleHeaders0.setLong(1, articleID);
   731       rs = this.pstmtGetArticleHeaders0.executeQuery();
   732       
   733       StringBuilder buf = new StringBuilder();
   734       if(rs.next())
   735       {
   736         for(;;)
   737         {
   738           buf.append(rs.getString(1)); // key
   739           buf.append(": ");
   740           String foldedValue = MimeUtility.fold(0, rs.getString(2));
   741           buf.append(foldedValue); // value
   742           if(rs.next())
   743           {
   744             buf.append("\r\n");
   745           }
   746           else
   747           {
   748             break;
   749           }
   750         }
   751       }
   752       
   753       return buf.toString();
   754     }
   755     catch(SQLException ex)
   756     {
   757       restartConnection(ex);
   758       return getArticleHeaders(articleID);
   759     }
   760     finally
   761     {
   762       if(rs != null)
   763       {
   764         try
   765         {
   766           rs.close();
   767         }
   768         catch(SQLException ex)
   769         {
   770           ex.printStackTrace();
   771         }
   772       }
   773     }
   774   }
   775 
   776   @Override
   777   public long getArticleIndex(Article article, Group group)
   778     throws StorageBackendException
   779   {
   780     ResultSet rs = null;
   781 
   782     try
   783     {
   784       this.pstmtGetArticleIndex.setString(1, article.getMessageID());
   785       this.pstmtGetArticleIndex.setLong(2, group.getInternalID());
   786       
   787       rs = this.pstmtGetArticleIndex.executeQuery();
   788       if(rs.next())
   789       {
   790         return rs.getLong(1);
   791       }
   792       else
   793       {
   794         return -1;
   795       }
   796     }
   797     catch(SQLException ex)
   798     {
   799       restartConnection(ex);
   800       return getArticleIndex(article, group);
   801     }
   802     finally
   803     {
   804       if(rs != null)
   805       {
   806         try
   807         {
   808           rs.close();
   809         }
   810         catch(SQLException ex)
   811         {
   812           ex.printStackTrace();
   813         }
   814       }
   815     }
   816   }
   817   
   818   /**
   819    * Returns a list of Long/Article Pairs.
   820    * @throws java.sql.SQLException
   821    */
   822   @Override
   823   public List<Pair<Long, ArticleHead>> getArticleHeads(Group group, long first,
   824     long last)
   825     throws StorageBackendException
   826   {
   827     ResultSet rs = null;
   828 
   829     try
   830     {
   831       this.pstmtGetArticleHeads.setLong(1, group.getInternalID());
   832       this.pstmtGetArticleHeads.setLong(2, first);
   833       this.pstmtGetArticleHeads.setLong(3, last);
   834       rs = pstmtGetArticleHeads.executeQuery();
   835 
   836       List<Pair<Long, ArticleHead>> articles 
   837         = new ArrayList<Pair<Long, ArticleHead>>();
   838 
   839       while (rs.next())
   840       {
   841         long aid  = rs.getLong("article_id");
   842         long aidx = rs.getLong("article_index");
   843         String headers = getArticleHeaders(aid);
   844         articles.add(new Pair<Long, ArticleHead>(aidx, 
   845                         new ArticleHead(headers)));
   846       }
   847 
   848       return articles;
   849     }
   850     catch(SQLException ex)
   851     {
   852       restartConnection(ex);
   853       return getArticleHeads(group, first, last);
   854     }
   855     finally
   856     {
   857       if(rs != null)
   858       {
   859         try
   860         {
   861           rs.close();
   862         }
   863         catch(SQLException ex)
   864         {
   865           ex.printStackTrace();
   866         }
   867       }
   868     }
   869   }
   870 
   871   @Override
   872   public List<Long> getArticleNumbers(long gid)
   873     throws StorageBackendException
   874   {
   875     ResultSet rs = null;
   876     try
   877     {
   878       List<Long> ids = new ArrayList<Long>();
   879       this.pstmtGetArticleIDs.setLong(1, gid);
   880       rs = this.pstmtGetArticleIDs.executeQuery();
   881       while(rs.next())
   882       {
   883         ids.add(rs.getLong(1));
   884       }
   885       return ids;
   886     }
   887     catch(SQLException ex)
   888     {
   889       restartConnection(ex);
   890       return getArticleNumbers(gid);
   891     }
   892     finally
   893     {
   894       if(rs != null)
   895       {
   896         try
   897         {
   898           rs.close();
   899           restarts = 0; // Clear the restart count after successful request
   900         }
   901         catch(SQLException ex)
   902         {
   903           ex.printStackTrace();
   904         }
   905       }
   906     }
   907   }
   908 
   909   @Override
   910   public String getConfigValue(String key)
   911     throws StorageBackendException
   912   {
   913     ResultSet rs = null;
   914     try
   915     {
   916       this.pstmtGetConfigValue.setString(1, key);
   917 
   918       rs = this.pstmtGetConfigValue.executeQuery();
   919       if(rs.next())
   920       {
   921         return rs.getString(1); // First data on index 1 not 0
   922       }
   923       else
   924       {
   925         return null;
   926       }
   927     }
   928     catch(SQLException ex)
   929     {
   930       restartConnection(ex);
   931       return getConfigValue(key);
   932     }
   933     finally
   934     {
   935       if(rs != null)
   936       {
   937         try
   938         {
   939           rs.close();
   940         }
   941         catch(SQLException ex)
   942         {
   943           ex.printStackTrace();
   944         }
   945         restarts = 0; // Clear the restart count after successful request
   946       }
   947     }
   948   }
   949 
   950   @Override
   951   public int getEventsCount(int type, long start, long end, Channel channel)
   952     throws StorageBackendException
   953   {
   954     ResultSet rs = null;
   955     
   956     try
   957     {
   958       if(channel == null)
   959       {
   960         this.pstmtGetEventsCount0.setInt(1, type);
   961         this.pstmtGetEventsCount0.setLong(2, start);
   962         this.pstmtGetEventsCount0.setLong(3, end);
   963         rs = this.pstmtGetEventsCount0.executeQuery();
   964       }
   965       else
   966       {
   967         this.pstmtGetEventsCount1.setInt(1, type);
   968         this.pstmtGetEventsCount1.setLong(2, start);
   969         this.pstmtGetEventsCount1.setLong(3, end);
   970         this.pstmtGetEventsCount1.setLong(4, channel.getInternalID());
   971         rs = this.pstmtGetEventsCount1.executeQuery();
   972       }
   973       
   974       if(rs.next())
   975       {
   976         return rs.getInt(1);
   977       }
   978       else
   979       {
   980         return -1;
   981       }
   982     }
   983     catch(SQLException ex)
   984     {
   985       restartConnection(ex);
   986       return getEventsCount(type, start, end, channel);
   987     }
   988     finally
   989     {
   990       if(rs != null)
   991       {
   992         try
   993         {
   994           rs.close();
   995         }
   996         catch(SQLException ex)
   997         {
   998           ex.printStackTrace();
   999         }
  1000       }
  1001     }
  1002   }
  1003   
  1004   /**
  1005    * Reads all Groups from the JDBCDatabase.
  1006    * @return
  1007    * @throws StorageBackendException
  1008    */
  1009   @Override
  1010   public List<Channel> getGroups()
  1011     throws StorageBackendException
  1012   {
  1013     ResultSet   rs;
  1014     List<Channel> buffer = new ArrayList<Channel>();
  1015     Statement   stmt   = null;
  1016 
  1017     try
  1018     {
  1019       stmt = conn.createStatement();
  1020       rs = stmt.executeQuery("SELECT * FROM groups ORDER BY name");
  1021 
  1022       while(rs.next())
  1023       {
  1024         String name  = rs.getString("name");
  1025         long   id    = rs.getLong("group_id");
  1026         int    flags = rs.getInt("flags");
  1027         
  1028         Group group = new Group(name, id, flags);
  1029         buffer.add(group);
  1030       }
  1031 
  1032       return buffer;
  1033     }
  1034     catch(SQLException ex)
  1035     {
  1036       restartConnection(ex);
  1037       return getGroups();
  1038     }
  1039     finally
  1040     {
  1041       if(stmt != null)
  1042       {
  1043         try
  1044         {
  1045           stmt.close(); // Implicitely closes ResultSets
  1046         }
  1047         catch(SQLException ex)
  1048         {
  1049           ex.printStackTrace();
  1050         }
  1051       }
  1052     }
  1053   }
  1054 
  1055   @Override
  1056   public List<String> getGroupsForList(InternetAddress listAddress)
  1057     throws StorageBackendException
  1058   {
  1059     ResultSet rs = null;
  1060     
  1061     try
  1062     {
  1063       this.pstmtGetGroupForList.setString(1, listAddress.getAddress());
  1064 
  1065       rs = this.pstmtGetGroupForList.executeQuery();
  1066       List<String> groups = new ArrayList<String>();
  1067       while(rs.next())
  1068       {
  1069         String group = rs.getString(1);
  1070         groups.add(group);
  1071       }
  1072       return groups;
  1073     }
  1074     catch(SQLException ex)
  1075     {
  1076       restartConnection(ex);
  1077       return getGroupsForList(listAddress);
  1078     }
  1079     finally
  1080     {
  1081       if(rs != null)
  1082       {
  1083         try
  1084         {
  1085           rs.close();
  1086         }
  1087         catch(SQLException ex)
  1088         {
  1089           ex.printStackTrace();
  1090         }
  1091       }
  1092     }
  1093   }
  1094   
  1095   /**
  1096    * Returns the Group that is identified by the name.
  1097    * @param name
  1098    * @return
  1099    * @throws StorageBackendException
  1100    */
  1101   @Override
  1102   public Group getGroup(String name)
  1103     throws StorageBackendException
  1104   {
  1105     ResultSet rs = null;
  1106     
  1107     try
  1108     {
  1109       this.pstmtGetGroup0.setString(1, name);
  1110       rs = this.pstmtGetGroup0.executeQuery();
  1111 
  1112       if (!rs.next())
  1113       {
  1114         return null;
  1115       }
  1116       else
  1117       {
  1118         long id = rs.getLong("group_id");
  1119         int flags = rs.getInt("flags");
  1120         return new Group(name, id, flags);
  1121       }
  1122     }
  1123     catch(SQLException ex)
  1124     {
  1125       restartConnection(ex);
  1126       return getGroup(name);
  1127     }
  1128     finally
  1129     {
  1130       if(rs != null)
  1131       {
  1132         try
  1133         {
  1134           rs.close();
  1135         }
  1136         catch(SQLException ex)
  1137         {
  1138           ex.printStackTrace();
  1139         }
  1140       }
  1141     }
  1142   }
  1143 
  1144   @Override
  1145   public List<String> getListsForGroup(String group)
  1146     throws StorageBackendException
  1147   {
  1148     ResultSet     rs    = null;
  1149     List<String>  lists = new ArrayList<String>();
  1150 
  1151     try
  1152     {
  1153       this.pstmtGetListForGroup.setString(1, group);
  1154       rs = this.pstmtGetListForGroup.executeQuery();
  1155 
  1156       while(rs.next())
  1157       {
  1158         lists.add(rs.getString(1));
  1159       }
  1160       return lists;
  1161     }
  1162     catch(SQLException ex)
  1163     {
  1164       restartConnection(ex);
  1165       return getListsForGroup(group);
  1166     }
  1167     finally
  1168     {
  1169       if(rs != null)
  1170       {
  1171         try
  1172         {
  1173           rs.close();
  1174         }
  1175         catch(SQLException ex)
  1176         {
  1177           ex.printStackTrace();
  1178         }
  1179       }
  1180     }
  1181   }
  1182   
  1183   private int getMaxArticleIndex(long groupID)
  1184     throws StorageBackendException
  1185   {
  1186     ResultSet rs    = null;
  1187 
  1188     try
  1189     {
  1190       this.pstmtGetMaxArticleIndex.setLong(1, groupID);
  1191       rs = this.pstmtGetMaxArticleIndex.executeQuery();
  1192 
  1193       int maxIndex = 0;
  1194       if (rs.next())
  1195       {
  1196         maxIndex = rs.getInt(1);
  1197       }
  1198 
  1199       return maxIndex;
  1200     }
  1201     catch(SQLException ex)
  1202     {
  1203       restartConnection(ex);
  1204       return getMaxArticleIndex(groupID);
  1205     }
  1206     finally
  1207     {
  1208       if(rs != null)
  1209       {
  1210         try
  1211         {
  1212           rs.close();
  1213         }
  1214         catch(SQLException ex)
  1215         {
  1216           ex.printStackTrace();
  1217         }
  1218       }
  1219     }
  1220   }
  1221   
  1222   private int getMaxArticleID()
  1223     throws StorageBackendException
  1224   {
  1225     ResultSet rs    = null;
  1226 
  1227     try
  1228     {
  1229       rs = this.pstmtGetMaxArticleID.executeQuery();
  1230 
  1231       int maxIndex = 0;
  1232       if (rs.next())
  1233       {
  1234         maxIndex = rs.getInt(1);
  1235       }
  1236 
  1237       return maxIndex;
  1238     }
  1239     catch(SQLException ex)
  1240     {
  1241       restartConnection(ex);
  1242       return getMaxArticleID();
  1243     }
  1244     finally
  1245     {
  1246       if(rs != null)
  1247       {
  1248         try
  1249         {
  1250           rs.close();
  1251         }
  1252         catch(SQLException ex)
  1253         {
  1254           ex.printStackTrace();
  1255         }
  1256       }
  1257     }
  1258   }
  1259 
  1260   @Override
  1261   public int getLastArticleNumber(Group group)
  1262     throws StorageBackendException
  1263   {
  1264     ResultSet rs = null;
  1265 
  1266     try
  1267     {
  1268       this.pstmtGetLastArticleNumber.setLong(1, group.getInternalID());
  1269       rs = this.pstmtGetLastArticleNumber.executeQuery();
  1270       if (rs.next())
  1271       {
  1272         return rs.getInt(1);
  1273       }
  1274       else
  1275       {
  1276         return 0;
  1277       }
  1278     }
  1279     catch(SQLException ex)
  1280     {
  1281       restartConnection(ex);
  1282       return getLastArticleNumber(group);
  1283     }
  1284     finally
  1285     {
  1286       if(rs != null)
  1287       {
  1288         try
  1289         {
  1290           rs.close();
  1291         }
  1292         catch(SQLException ex)
  1293         {
  1294           ex.printStackTrace();
  1295         }
  1296       }
  1297     }
  1298   }
  1299 
  1300   @Override
  1301   public int getFirstArticleNumber(Group group)
  1302     throws StorageBackendException
  1303   {
  1304     ResultSet rs = null;
  1305     try
  1306     {
  1307       this.pstmtGetFirstArticleNumber.setLong(1, group.getInternalID());
  1308       rs = this.pstmtGetFirstArticleNumber.executeQuery();
  1309       if(rs.next())
  1310       {
  1311         return rs.getInt(1);
  1312       }
  1313       else
  1314       {
  1315         return 0;
  1316       }
  1317     }
  1318     catch(SQLException ex)
  1319     {
  1320       restartConnection(ex);
  1321       return getFirstArticleNumber(group);
  1322     }
  1323     finally
  1324     {
  1325       if(rs != null)
  1326       {
  1327         try
  1328         {
  1329           rs.close();
  1330         }
  1331         catch(SQLException ex)
  1332         {
  1333           ex.printStackTrace();
  1334         }
  1335       }
  1336     }
  1337   }
  1338   
  1339   /**
  1340    * Returns a group name identified by the given id.
  1341    * @param id
  1342    * @return
  1343    * @throws StorageBackendException
  1344    */
  1345   public String getGroup(int id)
  1346     throws StorageBackendException
  1347   {
  1348     ResultSet rs = null;
  1349 
  1350     try
  1351     {
  1352       this.pstmtGetGroup1.setInt(1, id);
  1353       rs = this.pstmtGetGroup1.executeQuery();
  1354 
  1355       if (rs.next())
  1356       {
  1357         return rs.getString(1);
  1358       }
  1359       else
  1360       {
  1361         return null;
  1362       }
  1363     }
  1364     catch(SQLException ex)
  1365     {
  1366       restartConnection(ex);
  1367       return getGroup(id);
  1368     }
  1369     finally
  1370     {
  1371       if(rs != null)
  1372       {
  1373         try
  1374         {
  1375           rs.close();
  1376         }
  1377         catch(SQLException ex)
  1378         {
  1379           ex.printStackTrace();
  1380         }
  1381       }
  1382     }
  1383   }
  1384 
  1385   @Override
  1386   public double getEventsPerHour(int key, long gid)
  1387     throws StorageBackendException
  1388   {
  1389     String gidquery = "";
  1390     if(gid >= 0)
  1391     {
  1392       gidquery = " AND group_id = " + gid;
  1393     }
  1394     
  1395     Statement stmt = null;
  1396     ResultSet rs   = null;
  1397     
  1398     try
  1399     {
  1400       stmt = this.conn.createStatement();
  1401       rs = stmt.executeQuery("SELECT Count(*) / (Max(event_time) - Min(event_time))" +
  1402         " * 1000 * 60 * 60 FROM events WHERE event_key = " + key + gidquery);
  1403       
  1404       if(rs.next())
  1405       {
  1406         restarts = 0; // reset error count
  1407         return rs.getDouble(1);
  1408       }
  1409       else
  1410       {
  1411         return Double.NaN;
  1412       }
  1413     }
  1414     catch(SQLException ex)
  1415     {
  1416       restartConnection(ex);
  1417       return getEventsPerHour(key, gid);
  1418     }
  1419     finally
  1420     {
  1421       try
  1422       {
  1423         if(stmt != null)
  1424         {
  1425           stmt.close(); // Implicitely closes the result sets
  1426         }
  1427       }
  1428       catch(SQLException ex)
  1429       {
  1430         ex.printStackTrace();
  1431       }
  1432     }
  1433   }
  1434 
  1435   @Override
  1436   public String getOldestArticle()
  1437     throws StorageBackendException
  1438   {
  1439     ResultSet rs = null;
  1440 
  1441     try
  1442     {
  1443       rs = this.pstmtGetOldestArticle.executeQuery();
  1444       if(rs.next())
  1445       {
  1446         return rs.getString(1);
  1447       }
  1448       else
  1449       {
  1450         return null;
  1451       }
  1452     }
  1453     catch(SQLException ex)
  1454     {
  1455       restartConnection(ex);
  1456       return getOldestArticle();
  1457     }
  1458     finally
  1459     {
  1460       if(rs != null)
  1461       {
  1462         try
  1463         {
  1464           rs.close();
  1465         }
  1466         catch(SQLException ex)
  1467         {
  1468           ex.printStackTrace();
  1469         }
  1470       }
  1471     }
  1472   }
  1473 
  1474   @Override
  1475   public int getPostingsCount(String groupname)
  1476     throws StorageBackendException
  1477   {
  1478     ResultSet rs = null;
  1479     
  1480     try
  1481     {
  1482       this.pstmtGetPostingsCount.setString(1, groupname);
  1483       rs = this.pstmtGetPostingsCount.executeQuery();
  1484       if(rs.next())
  1485       {
  1486         return rs.getInt(1);
  1487       }
  1488       else
  1489       {
  1490         Log.msg("Warning: Count on postings return nothing!", true);
  1491         return 0;
  1492       }
  1493     }
  1494     catch(SQLException ex)
  1495     {
  1496       restartConnection(ex);
  1497       return getPostingsCount(groupname);
  1498     }
  1499     finally
  1500     {
  1501       if(rs != null)
  1502       {
  1503         try
  1504         {
  1505           rs.close();
  1506         }
  1507         catch(SQLException ex)
  1508         {
  1509           ex.printStackTrace();
  1510         }
  1511       }
  1512     }
  1513   }
  1514 
  1515   @Override
  1516   public List<Subscription> getSubscriptions(int feedtype)
  1517     throws StorageBackendException
  1518   {
  1519     ResultSet rs = null;
  1520     
  1521     try
  1522     {
  1523       List<Subscription> subs = new ArrayList<Subscription>();
  1524       this.pstmtGetSubscriptions.setInt(1, feedtype);
  1525       rs = this.pstmtGetSubscriptions.executeQuery();
  1526       
  1527       while(rs.next())
  1528       {
  1529         String host  = rs.getString("host");
  1530         String group = rs.getString("name");
  1531         int    port  = rs.getInt("port");
  1532         subs.add(new Subscription(host, port, feedtype, group));
  1533       }
  1534       
  1535       return subs;
  1536     }
  1537     catch(SQLException ex)
  1538     {
  1539       restartConnection(ex);
  1540       return getSubscriptions(feedtype);
  1541     }
  1542     finally
  1543     {
  1544       if(rs != null)
  1545       {
  1546         try
  1547         {
  1548           rs.close();
  1549         }
  1550         catch(SQLException ex)
  1551         {
  1552           ex.printStackTrace();
  1553         }
  1554       }
  1555     }
  1556   }
  1557 
  1558   /**
  1559    * Checks if there is an article with the given messageid in the JDBCDatabase.
  1560    * @param name
  1561    * @return
  1562    * @throws StorageBackendException
  1563    */
  1564   @Override
  1565   public boolean isArticleExisting(String messageID)
  1566     throws StorageBackendException
  1567   {
  1568     ResultSet rs = null;
  1569     
  1570     try
  1571     {
  1572       this.pstmtIsArticleExisting.setString(1, messageID);
  1573       rs = this.pstmtIsArticleExisting.executeQuery();
  1574       return rs.next() && rs.getInt(1) == 1;
  1575     }
  1576     catch(SQLException ex)
  1577     {
  1578       restartConnection(ex);
  1579       return isArticleExisting(messageID);
  1580     }
  1581     finally
  1582     {
  1583       if(rs != null)
  1584       {
  1585         try
  1586         {
  1587           rs.close();
  1588         }
  1589         catch(SQLException ex)
  1590         {
  1591           ex.printStackTrace();
  1592         }
  1593       }
  1594     }
  1595   }
  1596   
  1597   /**
  1598    * Checks if there is a group with the given name in the JDBCDatabase.
  1599    * @param name
  1600    * @return
  1601    * @throws StorageBackendException
  1602    */
  1603   @Override
  1604   public boolean isGroupExisting(String name)
  1605     throws StorageBackendException
  1606   {
  1607     ResultSet rs = null;
  1608     
  1609     try
  1610     {
  1611       this.pstmtIsGroupExisting.setString(1, name);
  1612       rs = this.pstmtIsGroupExisting.executeQuery();
  1613       return rs.next();
  1614     }
  1615     catch(SQLException ex)
  1616     {
  1617       restartConnection(ex);
  1618       return isGroupExisting(name);
  1619     }
  1620     finally
  1621     {
  1622       if(rs != null)
  1623       {
  1624         try
  1625         {
  1626           rs.close();
  1627         }
  1628         catch(SQLException ex)
  1629         {
  1630           ex.printStackTrace();
  1631         }
  1632       }
  1633     }
  1634   }
  1635 
  1636   @Override
  1637   public void setConfigValue(String key, String value)
  1638     throws StorageBackendException
  1639   {
  1640     try
  1641     {
  1642       conn.setAutoCommit(false);
  1643       this.pstmtSetConfigValue0.setString(1, key);
  1644       this.pstmtSetConfigValue0.execute();
  1645       this.pstmtSetConfigValue1.setString(1, key);
  1646       this.pstmtSetConfigValue1.setString(2, value);
  1647       this.pstmtSetConfigValue1.execute();
  1648       conn.commit();
  1649       conn.setAutoCommit(true);
  1650     }
  1651     catch(SQLException ex)
  1652     {
  1653       restartConnection(ex);
  1654       setConfigValue(key, value);
  1655     }
  1656   }
  1657   
  1658   /**
  1659    * Closes the JDBCDatabase connection.
  1660    */
  1661   public void shutdown()
  1662     throws StorageBackendException
  1663   {
  1664     try
  1665     {
  1666       if(this.conn != null)
  1667       {
  1668         this.conn.close();
  1669       }
  1670     }
  1671     catch(SQLException ex)
  1672     {
  1673       throw new StorageBackendException(ex);
  1674     }
  1675   }
  1676 
  1677   @Override
  1678   public void purgeGroup(Group group)
  1679     throws StorageBackendException
  1680   {
  1681     try
  1682     {
  1683       this.pstmtPurgeGroup0.setLong(1, group.getInternalID());
  1684       this.pstmtPurgeGroup0.executeUpdate();
  1685 
  1686       this.pstmtPurgeGroup1.setLong(1, group.getInternalID());
  1687       this.pstmtPurgeGroup1.executeUpdate();
  1688     }
  1689     catch(SQLException ex)
  1690     {
  1691       restartConnection(ex);
  1692       purgeGroup(group);
  1693     }
  1694   }
  1695   
  1696   private void restartConnection(SQLException cause)
  1697     throws StorageBackendException
  1698   {
  1699     restarts++;
  1700     Log.msg(Thread.currentThread() 
  1701       + ": Database connection was closed (restart " + restarts + ").", false);
  1702     
  1703     if(restarts >= MAX_RESTARTS)
  1704     {
  1705       // Delete the current, probably broken JDBCDatabase instance.
  1706       // So no one can use the instance any more.
  1707       JDBCDatabaseProvider.instances.remove(Thread.currentThread());
  1708       
  1709       // Throw the exception upwards
  1710       throw new StorageBackendException(cause);
  1711     }
  1712     
  1713     try
  1714     {
  1715       Thread.sleep(1500L * restarts);
  1716     }
  1717     catch(InterruptedException ex)
  1718     {
  1719       Log.msg("Interrupted: " + ex.getMessage(), false);
  1720     }
  1721     
  1722     // Try to properly close the old database connection
  1723     try
  1724     {
  1725       if(this.conn != null)
  1726       {
  1727         this.conn.close();
  1728       }
  1729     }
  1730     catch(SQLException ex)
  1731     {
  1732       Log.msg(ex.getMessage(), true);
  1733     }
  1734     
  1735     try
  1736     {
  1737       // Try to reinitialize database connection
  1738       arise();
  1739     }
  1740     catch(SQLException ex)
  1741     {
  1742       Log.msg(ex.getMessage(), true);
  1743       restartConnection(ex);
  1744     }
  1745   }
  1746 
  1747   /**
  1748    * Writes the flags and the name of the given group to the database.
  1749    * @param group
  1750    * @throws StorageBackendException
  1751    */
  1752   @Override
  1753   public boolean update(Group group)
  1754     throws StorageBackendException
  1755   {
  1756     try
  1757     {
  1758       this.pstmtUpdateGroup.setInt(1, group.getFlags());
  1759       this.pstmtUpdateGroup.setString(2, group.getName());
  1760       this.pstmtUpdateGroup.setLong(3, group.getInternalID());
  1761       int rs = this.pstmtUpdateGroup.executeUpdate();
  1762       return rs == 1;
  1763     }
  1764     catch(SQLException ex)
  1765     {
  1766       restartConnection(ex);
  1767       return update(group);
  1768     }
  1769   }
  1770 
  1771 }