KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > mailrepository > JDBCMailRepository


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 package org.apache.james.mailrepository;
19
20 import org.apache.avalon.cornerstone.services.datasource.DataSourceSelector;
21 import org.apache.avalon.cornerstone.services.store.Store;
22 import org.apache.avalon.cornerstone.services.store.StreamRepository;
23 import org.apache.avalon.excalibur.datasource.DataSourceComponent;
24 import org.apache.avalon.framework.activity.Initializable;
25 import org.apache.avalon.framework.component.Component;
26 import org.apache.avalon.framework.component.ComponentException;
27 import org.apache.avalon.framework.component.ComponentManager;
28 import org.apache.avalon.framework.component.Composable;
29 import org.apache.avalon.framework.configuration.Configurable;
30 import org.apache.avalon.framework.configuration.Configuration;
31 import org.apache.avalon.framework.configuration.ConfigurationException;
32 import org.apache.avalon.framework.configuration.DefaultConfiguration;
33 import org.apache.avalon.framework.context.Context;
34 import org.apache.avalon.framework.context.ContextException;
35 import org.apache.avalon.framework.context.Contextualizable;
36 import org.apache.avalon.framework.logger.AbstractLogEnabled;
37 import org.apache.james.context.AvalonContextUtilities;
38 import org.apache.james.core.MailImpl;
39 import org.apache.james.core.MimeMessageWrapper;
40 import org.apache.james.services.MailRepository;
41 import org.apache.james.util.JDBCUtil;
42 import org.apache.james.util.Lock;
43 import org.apache.james.util.SqlResources;
44 import org.apache.mailet.MailAddress;
45
46 import javax.mail.MessagingException JavaDoc;
47 import javax.mail.internet.MimeMessage JavaDoc;
48 import java.io.ByteArrayInputStream JavaDoc;
49 import java.io.ByteArrayOutputStream JavaDoc;
50 import java.io.File JavaDoc;
51 import java.io.IOException JavaDoc;
52 import java.io.ObjectOutputStream JavaDoc;
53 import java.io.ObjectInputStream JavaDoc;
54 import java.io.OutputStream JavaDoc;
55 import java.sql.*;
56 import java.util.*;
57
58 /**
59  * Implementation of a MailRepository on a database.
60  *
61  * <p>Requires a configuration element in the .conf.xml file of the form:
62  * <br>&lt;repository destinationURL="db://&lt;datasource&gt;/&lt;table_name&gt;/&lt;repository_name&gt;"
63  * <br> type="MAIL"
64  * <br> model="SYNCHRONOUS"/&gt;
65  * <br>&lt;/repository&gt;
66  * <p>destinationURL specifies..(Serge??)
67  * <br>Type can be SPOOL or MAIL
68  * <br>Model is currently not used and may be dropped
69  *
70  * <p>Requires a logger called MailRepository.
71  *
72  * @version CVS $Revision: 1.30.4.16 $ $Date: 2004/05/19 10:40:03 $
73  */

74 public class JDBCMailRepository
75     extends AbstractLogEnabled
76     implements MailRepository, Component, Contextualizable, Composable, Configurable, Initializable {
77
78     /**
79      * Whether 'deep debugging' is turned on.
80      */

81     private static final boolean DEEP_DEBUG = false;
82
83     /**
84      * The Avalon componentManager used by the instance
85      */

86     private ComponentManager componentManager;
87
88     /**
89      * The Avalon context used by the instance
90      */

91     protected Context context;
92
93     /**
94      * A lock used to control access to repository elements, locking access
95      * based on the key
96      */

97     private Lock lock;
98
99     // Configuration elements
100

101     /**
102      * Destination URL for the repository. See class description for more info
103      */

104 // protected String destination;
105

106     /**
107      * The table name parsed from the destination URL
108      */

109     protected String JavaDoc tableName;
110
111     /**
112      * The repository name parsed from the destination URL
113      */

114     protected String JavaDoc repositoryName;
115
116     /**
117      * The name of the filestore to be used to store mail when configured to use dbfile mode.
118      */

119 // protected String filestore;
120

121     /**
122      * The name of the SQL configuration file to be used to configure this repository.
123      */

124     private String JavaDoc sqlFileName;
125
126     /**
127      * The stream repository used in dbfile mode
128      */

129     private StreamRepository sr = null;
130
131     //The data-source for this repository
132

133     /**
134      * The selector used to obtain the JDBC datasource
135      */

136     protected DataSourceSelector datasources;
137
138     /**
139      * The JDBC datasource that provides the JDBC connection
140      */

141     protected DataSourceComponent datasource;
142
143     /**
144      * The name of the datasource used by this repository
145      */

146     protected String JavaDoc datasourceName;
147
148     /**
149      * Contains all of the sql strings for this component.
150      */

151     protected SqlResources sqlQueries;
152
153     /**
154      * The JDBCUtil helper class
155      */

156     protected JDBCUtil theJDBCUtil;
157     
158     /**
159      * "Support for Mail Attributes under JDBC repositories is ready" indicator.
160      */

161     protected boolean jdbcMailAttributesReady = false;
162
163     /**
164      * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
165      */

166     public void contextualize(final Context context)
167             throws ContextException {
168         this.context = context;
169     }
170
171     /**
172      * @see org.apache.avalon.framework.component.Composable#compose(ComponentManager)
173      */

174     public void compose( final ComponentManager componentManager )
175         throws ComponentException {
176         StringBuffer JavaDoc logBuffer = null;
177         if (getLogger().isDebugEnabled()) {
178             logBuffer =
179                 new StringBuffer JavaDoc(64)
180                         .append(this.getClass().getName())
181                         .append(".compose()");
182             getLogger().debug(logBuffer.toString());
183         }
184         // Get the DataSourceSelector service
185
datasources = (DataSourceSelector)componentManager.lookup( DataSourceSelector.ROLE );
186         this.componentManager = componentManager;
187
188     }
189
190     /**
191      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
192      */

193     public void configure(Configuration conf) throws ConfigurationException {
194         if (getLogger().isDebugEnabled()) {
195             getLogger().debug(this.getClass().getName() + ".configure()");
196         }
197
198         String JavaDoc destination = conf.getAttribute("destinationURL");
199         // normalize the destination, to simplify processing.
200
if ( ! destination.endsWith("/") ) {
201             destination += "/";
202         }
203         // Parse the DestinationURL for the name of the datasource,
204
// the table to use, and the (optional) repository Key.
205
// Split on "/", starting after "db://"
206
List urlParams = new ArrayList();
207         int start = 5;
208         if (destination.startsWith("dbfile")) {
209             //this is dbfile:// instead of db://
210
start += 4;
211         }
212         int end = destination.indexOf('/', start);
213         while ( end > -1 ) {
214             urlParams.add(destination.substring(start, end));
215             start = end + 1;
216             end = destination.indexOf('/', start);
217         }
218
219         // Build SqlParameters and get datasource name from URL parameters
220
if (urlParams.size() == 0) {
221             StringBuffer JavaDoc exceptionBuffer =
222                 new StringBuffer JavaDoc(256)
223                         .append("Malformed destinationURL - Must be of the format '")
224                         .append("db://<data-source>[/<table>[/<repositoryName>]]'. Was passed ")
225                         .append(conf.getAttribute("destinationURL"));
226             throw new ConfigurationException(exceptionBuffer.toString());
227         }
228         if (urlParams.size() >= 1) {
229             datasourceName = (String JavaDoc)urlParams.get(0);
230         }
231         if (urlParams.size() >= 2) {
232             tableName = (String JavaDoc)urlParams.get(1);
233         }
234         if (urlParams.size() >= 3) {
235             repositoryName = "";
236             for (int i = 2; i < urlParams.size(); i++) {
237                 if (i >= 3) {
238                     repositoryName += '/';
239                 }
240                 repositoryName += (String JavaDoc)urlParams.get(i);
241             }
242         }
243
244         if (getLogger().isDebugEnabled()) {
245             StringBuffer JavaDoc logBuffer =
246                 new StringBuffer JavaDoc(128)
247                         .append("Parsed URL: table = '")
248                         .append(tableName)
249                         .append("', repositoryName = '")
250                         .append(repositoryName)
251                         .append("'");
252             getLogger().debug(logBuffer.toString());
253         }
254
255         String JavaDoc filestore = conf.getChild("filestore").getValue(null);
256         sqlFileName = conf.getChild("sqlFile").getValue();
257         if (!sqlFileName.startsWith("file://")) {
258             throw new ConfigurationException
259                 ("Malformed sqlFile - Must be of the format 'file://<filename>'.");
260         }
261         try {
262             if (filestore != null) {
263                 Store store = (Store)componentManager.
264                         lookup("org.apache.avalon.cornerstone.services.store.Store");
265                 //prepare Configurations for stream repositories
266
DefaultConfiguration streamConfiguration
267                     = new DefaultConfiguration( "repository",
268                                                 "generated:JDBCMailRepository.configure()" );
269
270                 streamConfiguration.setAttribute( "destinationURL", filestore );
271                 streamConfiguration.setAttribute( "type", "STREAM" );
272                 streamConfiguration.setAttribute( "model", "SYNCHRONOUS" );
273                 sr = (StreamRepository) store.select(streamConfiguration);
274
275                 if (getLogger().isDebugEnabled()) {
276                     getLogger().debug("Got filestore for JdbcMailRepository: " + filestore);
277                 }
278             }
279
280             lock = new Lock();
281             if (getLogger().isDebugEnabled()) {
282                 StringBuffer JavaDoc logBuffer =
283                     new StringBuffer JavaDoc(128)
284                             .append(this.getClass().getName())
285                             .append(" created according to ")
286                             .append(destination);
287                 getLogger().debug(logBuffer.toString());
288             }
289         } catch (Exception JavaDoc e) {
290             final String JavaDoc message = "Failed to retrieve Store component:" + e.getMessage();
291             getLogger().error(message, e);
292             throw new ConfigurationException(message, e);
293         }
294     }
295
296     /**
297      * Initialises the JDBC repository.
298      * 1) Tests the connection to the database.
299      * 2) Loads SQL strings from the SQL definition file,
300      * choosing the appropriate SQL for this connection,
301      * and performing paramter substitution,
302      * 3) Initialises the database with the required tables, if necessary.
303      *
304      * @throws Exception if an error occurs
305      */

306     public void initialize() throws Exception JavaDoc {
307         StringBuffer JavaDoc logBuffer = null;
308         if (getLogger().isDebugEnabled()) {
309             getLogger().debug(this.getClass().getName() + ".initialize()");
310         }
311
312         theJDBCUtil =
313             new JDBCUtil() {
314                 protected void delegatedLog(String JavaDoc logString) {
315                     JDBCMailRepository.this.getLogger().warn("JDBCMailRepository: " + logString);
316                 }
317             };
318         // Get the data-source required.
319
datasource = (DataSourceComponent)datasources.select(datasourceName);
320
321         // Test the connection to the database, by getting the DatabaseMetaData.
322
Connection conn = datasource.getConnection();
323         PreparedStatement createStatement = null;
324
325         try {
326             // Initialise the sql strings.
327

328             File JavaDoc sqlFile = null;
329             try {
330                 sqlFile = AvalonContextUtilities.getFile(context, sqlFileName);
331                 sqlFileName = null;
332             } catch (Exception JavaDoc e) {
333                 getLogger().fatalError(e.getMessage(), e);
334                 throw e;
335             }
336
337             String JavaDoc resourceName = "org.apache.james.mailrepository.JDBCMailRepository";
338
339             if (getLogger().isDebugEnabled()) {
340                 logBuffer =
341                     new StringBuffer JavaDoc(128)
342                             .append("Reading SQL resources from file: ")
343                             .append(sqlFile.getAbsolutePath())
344                             .append(", section ")
345                             .append(this.getClass().getName())
346                             .append(".");
347                 getLogger().debug(logBuffer.toString());
348             }
349
350             // Build the statement parameters
351
Map sqlParameters = new HashMap();
352             if (tableName != null) {
353                 sqlParameters.put("table", tableName);
354             }
355             if (repositoryName != null) {
356                 sqlParameters.put("repository", repositoryName);
357             }
358
359             sqlQueries = new SqlResources();
360             sqlQueries.init(sqlFile, this.getClass().getName(),
361                             conn, sqlParameters);
362
363             // Check if the required table exists. If not, create it.
364
DatabaseMetaData dbMetaData = conn.getMetaData();
365             // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo.
366
// Try UPPER, lower, and MixedCase, to see if the table is there.
367
if (!(theJDBCUtil.tableExists(dbMetaData, tableName))) {
368                 // Users table doesn't exist - create it.
369
createStatement =
370                     conn.prepareStatement(sqlQueries.getSqlString("createTable", true));
371                 createStatement.execute();
372
373                 if (getLogger().isInfoEnabled()) {
374                     logBuffer =
375                         new StringBuffer JavaDoc(64)
376                                 .append("JdbcMailRepository: Created table '")
377                                 .append(tableName)
378                                 .append("'.");
379                     getLogger().info(logBuffer.toString());
380                 }
381             }
382             
383             checkJdbcAttributesSupport(dbMetaData);
384
385         } finally {
386             theJDBCUtil.closeJDBCStatement(createStatement);
387             theJDBCUtil.closeJDBCConnection(conn);
388         }
389     }
390     
391     /** Checks whether support for JDBC Mail atributes is activated for this repository
392      * and if everything is consistent.
393      * Looks for both the "updateMessageAttributesSQL" and "retrieveMessageAttributesSQL"
394      * statements in sqlResources and for a table column named "message_attributes".
395      *
396      * @param dbMetaData the database metadata to be used to look up the column
397      * @throws SQLException if a fatal situation is met
398      */

399     protected void checkJdbcAttributesSupport(DatabaseMetaData dbMetaData) throws SQLException {
400         String JavaDoc attributesColumnName = "message_attributes";
401         boolean hasUpdateMessageAttributesSQL = false;
402         boolean hasRetrieveMessageAttributesSQL = false;
403         
404         boolean hasMessageAttributesColumn = theJDBCUtil.columnExists(dbMetaData, tableName, attributesColumnName);
405         
406         StringBuffer JavaDoc logBuffer = new StringBuffer JavaDoc(64)
407                                     .append("JdbcMailRepository '"
408                                             + repositoryName
409                                             + ", table '"
410                                             + tableName
411                                             + "': ");
412         
413         //Determine whether attributes are used and available for storing
414
//Do we have updateMessageAttributesSQL?
415
String JavaDoc updateMessageAttrSql =
416             sqlQueries.getSqlString("updateMessageAttributesSQL", false);
417         if (updateMessageAttrSql!=null) {
418             hasUpdateMessageAttributesSQL = true;
419         }
420         
421         //Determine whether attributes are used and retrieve them
422
//Do we have retrieveAttributesSQL?
423
String JavaDoc retrieveMessageAttrSql =
424             sqlQueries.getSqlString("retrieveMessageAttributesSQL", false);
425         if (retrieveMessageAttrSql!=null) {
426             hasRetrieveMessageAttributesSQL = true;
427         }
428         
429         if (hasUpdateMessageAttributesSQL && !hasRetrieveMessageAttributesSQL) {
430             logBuffer.append("JDBC Mail Attributes support was activated for update but not for retrieval"
431                              + "(found 'updateMessageAttributesSQL' but not 'retrieveMessageAttributesSQL'"
432                              + "in table '"
433                              + tableName
434                              + "').");
435             getLogger().fatalError(logBuffer.toString());
436             throw new SQLException(logBuffer.toString());
437         }
438         if (!hasUpdateMessageAttributesSQL && hasRetrieveMessageAttributesSQL) {
439             logBuffer.append("JDBC Mail Attributes support was activated for retrieval but not for update"
440                              + "(found 'retrieveMessageAttributesSQL' but not 'updateMessageAttributesSQL'"
441                              + "in table '"
442                              + tableName
443                              + "'.");
444             getLogger().fatalError(logBuffer.toString());
445             throw new SQLException(logBuffer.toString());
446         }
447         if (!hasMessageAttributesColumn
448             && (hasUpdateMessageAttributesSQL || hasRetrieveMessageAttributesSQL)
449             ) {
450                 logBuffer.append("JDBC Mail Attributes support was activated but column '"
451                                  + attributesColumnName
452                                  + "' is missing in table '"
453                                  + tableName
454                                  + "'.");
455                 getLogger().fatalError(logBuffer.toString());
456                 throw new SQLException(logBuffer.toString());
457         }
458         if (hasUpdateMessageAttributesSQL && hasRetrieveMessageAttributesSQL) {
459             jdbcMailAttributesReady = true;
460             if (getLogger().isInfoEnabled()) {
461                 logBuffer.append("JDBC Mail Attributes support ready.");
462                 getLogger().info(logBuffer.toString());
463             }
464         } else {
465             jdbcMailAttributesReady = false;
466             logBuffer.append("JDBC Mail Attributes support not activated. "
467                              + "Missing both 'updateMessageAttributesSQL' "
468                              + "and 'retrieveMessageAttributesSQL' "
469                              + "statements for table '"
470                              + tableName
471                              + "' in sqlResources.xml. "
472                              + "Will not persist in the repository '"
473                              + repositoryName
474                              + "'.");
475             getLogger().warn(logBuffer.toString());
476         }
477     }
478
479     /**
480      * Releases a lock on a message identified by a key
481      *
482      * @param key the key of the message to be unlocked
483      *
484      * @return true if successfully released the lock, false otherwise
485      */

486     public synchronized boolean unlock(String JavaDoc key) {
487         if (lock.unlock(key)) {
488             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
489                 StringBuffer JavaDoc debugBuffer =
490                     new StringBuffer JavaDoc(256)
491                             .append("Unlocked ")
492                             .append(key)
493                             .append(" for ")
494                             .append(Thread.currentThread().getName())
495                             .append(" @ ")
496                             .append(new java.util.Date JavaDoc(System.currentTimeMillis()));
497                 getLogger().debug(debugBuffer.toString());
498             }
499 // notifyAll();
500
return true;
501         } else {
502             return false;
503         }
504     }
505
506     /**
507      * Obtains a lock on a message identified by a key
508      *
509      * @param key the key of the message to be locked
510      *
511      * @return true if successfully obtained the lock, false otherwise
512      */

513     public synchronized boolean lock(String JavaDoc key) {
514         if (lock.lock(key)) {
515             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
516                 StringBuffer JavaDoc debugBuffer =
517                     new StringBuffer JavaDoc(256)
518                             .append("Locked ")
519                             .append(key)
520                             .append(" for ")
521                             .append(Thread.currentThread().getName())
522                             .append(" @ ")
523                             .append(new java.util.Date JavaDoc(System.currentTimeMillis()));
524                 getLogger().debug(debugBuffer.toString());
525             }
526             return true;
527         } else {
528             return false;
529         }
530     }
531
532     /**
533      * Store this message to the database. Optionally stores the message
534      * body to the filesystem and only writes the headers to the database.
535      */

536     public void store(MailImpl mc) throws MessagingException JavaDoc {
537         Connection conn = null;
538         try {
539             conn = datasource.getConnection();
540
541             //Need to determine whether need to insert this record, or update it.
542

543             //Begin a transaction
544
conn.setAutoCommit(false);
545
546             PreparedStatement checkMessageExists = null;
547             ResultSet rsExists = null;
548             boolean exists = false;
549             try {
550                 checkMessageExists =
551                     conn.prepareStatement(sqlQueries.getSqlString("checkMessageExistsSQL", true));
552                 checkMessageExists.setString(1, mc.getName());
553                 checkMessageExists.setString(2, repositoryName);
554                 rsExists = checkMessageExists.executeQuery();
555                 exists = rsExists.next() && rsExists.getInt(1) > 0;
556             } finally {
557                 theJDBCUtil.closeJDBCResultSet(rsExists);
558                 theJDBCUtil.closeJDBCStatement(checkMessageExists);
559             }
560
561             if (exists) {
562                 //Update the existing record
563
PreparedStatement updateMessage = null;
564
565                 try {
566                     updateMessage =
567                         conn.prepareStatement(sqlQueries.getSqlString("updateMessageSQL", true));
568                     updateMessage.setString(1, mc.getState());
569                     updateMessage.setString(2, mc.getErrorMessage());
570                     if (mc.getSender() == null) {
571                         updateMessage.setNull(3, java.sql.Types.VARCHAR);
572                     } else {
573                         updateMessage.setString(3, mc.getSender().toString());
574                     }
575                     StringBuffer JavaDoc recipients = new StringBuffer JavaDoc();
576                     for (Iterator i = mc.getRecipients().iterator(); i.hasNext(); ) {
577                         recipients.append(i.next().toString());
578                         if (i.hasNext()) {
579                             recipients.append("\r\n");
580                         }
581                     }
582                     updateMessage.setString(4, recipients.toString());
583                     updateMessage.setString(5, mc.getRemoteHost());
584                     updateMessage.setString(6, mc.getRemoteAddr());
585                     updateMessage.setTimestamp(7, new java.sql.Timestamp JavaDoc(mc.getLastUpdated().getTime()));
586                     updateMessage.setString(8, mc.getName());
587                     updateMessage.setString(9, repositoryName);
588                     updateMessage.execute();
589                 } finally {
590                     Statement localUpdateMessage = updateMessage;
591                     // Clear reference to statement
592
updateMessage = null;
593                     theJDBCUtil.closeJDBCStatement(localUpdateMessage);
594                 }
595
596                 //Determine whether attributes are used and available for storing
597
if (jdbcMailAttributesReady && mc.hasAttributes()) {
598                     String JavaDoc updateMessageAttrSql =
599                         sqlQueries.getSqlString("updateMessageAttributesSQL", false);
600                     PreparedStatement updateMessageAttr = null;
601                     try {
602                         updateMessageAttr =
603                             conn.prepareStatement(updateMessageAttrSql);
604                         ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
605                         ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(baos);
606                         try {
607                             oos.writeObject(((MailImpl)mc).getAttributesRaw());
608                             oos.flush();
609                             ByteArrayInputStream JavaDoc attrInputStream =
610                                 new ByteArrayInputStream JavaDoc(baos.toByteArray());
611                             updateMessageAttr.setBinaryStream(1, attrInputStream, baos.size());
612                         } finally {
613                             try {
614                                 if (oos != null) {
615                                     oos.close();
616                                 }
617                             } catch (IOException JavaDoc ioe) {
618                                 getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.");
619                             }
620                         }
621                         updateMessageAttr.setString(2, mc.getName());
622                         updateMessageAttr.setString(3, repositoryName);
623                         updateMessageAttr.execute();
624                     } catch (SQLException sqle) {
625                         getLogger().info("JDBCMailRepository: Trying to update mail attributes failed.",sqle);
626                         
627                     } finally {
628                         theJDBCUtil.closeJDBCStatement(updateMessageAttr);
629                     }
630                 }
631
632                 //Determine whether the message body has changed, and possibly avoid
633
// updating the database.
634
MimeMessage JavaDoc messageBody = mc.getMessage();
635                 boolean saveBody = false;
636                 if (messageBody instanceof MimeMessageWrapper) {
637                     MimeMessageWrapper message = (MimeMessageWrapper)messageBody;
638                     saveBody = message.isModified();
639                 } else {
640                     saveBody = true;
641                 }
642
643                 if (saveBody) {
644                     try {
645                         updateMessage =
646                             conn.prepareStatement(sqlQueries.getSqlString("updateMessageBodySQL", true));
647                         ByteArrayOutputStream JavaDoc headerOut = new ByteArrayOutputStream JavaDoc();
648                         OutputStream JavaDoc bodyOut = null;
649                         try {
650                             if (sr == null) {
651                                 //If there is no filestore, use the byte array to store headers
652
// and the body
653
bodyOut = headerOut;
654                             } else {
655                                 //Store the body in the stream repository
656
bodyOut = sr.put(mc.getName());
657                             }
658         
659                             //Write the message to the headerOut and bodyOut. bodyOut goes straight to the file
660
MimeMessageWrapper.writeTo(messageBody, headerOut, bodyOut);
661         
662                             //Store the headers in the database
663
ByteArrayInputStream JavaDoc headerInputStream =
664                                 new ByteArrayInputStream JavaDoc(headerOut.toByteArray());
665                             updateMessage.setBinaryStream(1, headerInputStream, headerOut.size());
666                         } finally {
667                             closeOutputStreams(headerOut, bodyOut);
668                         }
669                         updateMessage.setString(2, mc.getName());
670                         updateMessage.setString(3, repositoryName);
671                         updateMessage.execute();
672                     } finally {
673                         theJDBCUtil.closeJDBCStatement(updateMessage);
674                     }
675                 }
676             } else {
677                 //Insert the record into the database
678
PreparedStatement insertMessage = null;
679                 try {
680                     String JavaDoc insertMessageSQL = sqlQueries.getSqlString("insertMessageSQL", true);
681                     int number_of_parameters = getNumberOfParameters (insertMessageSQL);
682                     insertMessage =
683                         conn.prepareStatement(insertMessageSQL);
684                     insertMessage.setString(1, mc.getName());
685                     insertMessage.setString(2, repositoryName);
686                     insertMessage.setString(3, mc.getState());
687                     insertMessage.setString(4, mc.getErrorMessage());
688                     if (mc.getSender() == null) {
689                         insertMessage.setNull(5, java.sql.Types.VARCHAR);
690                     } else {
691                         insertMessage.setString(5, mc.getSender().toString());
692                     }
693                     StringBuffer JavaDoc recipients = new StringBuffer JavaDoc();
694                     for (Iterator i = mc.getRecipients().iterator(); i.hasNext(); ) {
695                         recipients.append(i.next().toString());
696                         if (i.hasNext()) {
697                             recipients.append("\r\n");
698                         }
699                     }
700                     insertMessage.setString(6, recipients.toString());
701                     insertMessage.setString(7, mc.getRemoteHost());
702                     insertMessage.setString(8, mc.getRemoteAddr());
703                     insertMessage.setTimestamp(9, new java.sql.Timestamp JavaDoc(mc.getLastUpdated().getTime()));
704                     MimeMessage JavaDoc messageBody = mc.getMessage();
705     
706                     ByteArrayOutputStream JavaDoc headerOut = new ByteArrayOutputStream JavaDoc();
707                     OutputStream JavaDoc bodyOut = null;
708                     try {
709                         if (sr == null) {
710                             //If there is no sr, then use the same byte array to hold the headers
711
// and the body
712
bodyOut = headerOut;
713                         } else {
714                             //Store the body in the file system.
715
bodyOut = sr.put(mc.getName());
716                         }
717         
718                         //Write the message to the headerOut and bodyOut. bodyOut goes straight to the file
719
MimeMessageWrapper.writeTo(messageBody, headerOut, bodyOut);
720
721                         ByteArrayInputStream JavaDoc headerInputStream =
722                             new ByteArrayInputStream JavaDoc(headerOut.toByteArray());
723                         insertMessage.setBinaryStream(10, headerInputStream, headerOut.size());
724                     } finally {
725                         closeOutputStreams(headerOut, bodyOut);
726                     }
727                     //Store the headers in the database
728

729                     //Store attributes
730
if (number_of_parameters > 10) {
731                         ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
732                         ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(baos);
733                         try {
734                             oos.writeObject(((MailImpl)mc).getAttributesRaw());
735                             oos.flush();
736                             ByteArrayInputStream JavaDoc attrInputStream =
737                                 new ByteArrayInputStream JavaDoc(baos.toByteArray());
738                             insertMessage.setBinaryStream(11, attrInputStream, baos.size());
739                         } finally {
740                             try {
741                                 if (oos != null) {
742                                     oos.close();
743                                 }
744                             } catch (IOException JavaDoc ioe) {
745                                 getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.");
746                             }
747                         }
748                     }
749                     
750                     insertMessage.execute();
751                 } finally {
752                     theJDBCUtil.closeJDBCStatement(insertMessage);
753                 }
754             }
755
756             conn.commit();
757             conn.setAutoCommit(true);
758
759             synchronized (this) {
760 // notifyAll();
761
notify();
762             }
763         } catch (Exception JavaDoc e) {
764             throw new MessagingException JavaDoc("Exception caught while storing mail Container: " + e);
765         } finally {
766             theJDBCUtil.closeJDBCConnection(conn);
767         }
768     }
769
770     /**
771      * Retrieves a message given a key. At the moment, keys can be obtained
772      * from list()
773      *
774      * @param key the key of the message to retrieve
775      * @return the mail corresponding to this key, null if none exists
776      */

777     public MailImpl retrieve(String JavaDoc key) throws MessagingException JavaDoc {
778         if (DEEP_DEBUG) {
779             System.err.println("retrieving " + key);
780         }
781         Connection conn = null;
782         PreparedStatement retrieveMessage = null;
783         ResultSet rsMessage = null;
784         try {
785             conn = datasource.getConnection();
786             if (DEEP_DEBUG) {
787                 System.err.println("got a conn " + key);
788             }
789
790             retrieveMessage =
791                 conn.prepareStatement(sqlQueries.getSqlString("retrieveMessageSQL", true));
792             retrieveMessage.setString(1, key);
793             retrieveMessage.setString(2, repositoryName);
794             rsMessage = retrieveMessage.executeQuery();
795             if (DEEP_DEBUG) {
796                 System.err.println("ran the query " + key);
797             }
798             if (!rsMessage.next()) {
799                 if (getLogger().isDebugEnabled()) {
800                     StringBuffer JavaDoc debugBuffer =
801                         new StringBuffer JavaDoc(64)
802                                 .append("Did not find a record ")
803                                 .append(key)
804                                 .append(" in ")
805                                 .append(repositoryName);
806                     getLogger().debug(debugBuffer.toString());
807                 }
808                 return null;
809             }
810             //Determine whether attributes are used and retrieve them
811
PreparedStatement retrieveMessageAttr = null;
812             HashMap attributes = null;
813             if (jdbcMailAttributesReady) {
814                 String JavaDoc retrieveMessageAttrSql =
815                     sqlQueries.getSqlString("retrieveMessageAttributesSQL", false);
816                 ResultSet rsMessageAttr = null;
817                 try {
818                     retrieveMessageAttr =
819                         conn.prepareStatement(retrieveMessageAttrSql);
820                     
821                     retrieveMessageAttr.setString(1, key);
822                     retrieveMessageAttr.setString(2, repositoryName);
823                     rsMessageAttr = retrieveMessageAttr.executeQuery();
824                     
825                     if (rsMessageAttr.next()) {
826                         try {
827                             byte[] serialized_attr = null;
828                             String JavaDoc getAttributesOption = sqlQueries.getDbOption("getAttributes");
829                             if (getAttributesOption != null && getAttributesOption.equalsIgnoreCase("useBlob")) {
830                                 Blob b = rsMessageAttr.getBlob(1);
831                                 serialized_attr = b.getBytes(1, (int)b.length());
832                             } else {
833                                 serialized_attr = rsMessageAttr.getBytes(1);
834                             }
835                             // this check is for better backwards compatibility
836
if (serialized_attr != null) {
837                                 ByteArrayInputStream JavaDoc bais = new ByteArrayInputStream JavaDoc(serialized_attr);
838                                 ObjectInputStream JavaDoc ois = new ObjectInputStream JavaDoc(bais);
839                                 attributes = (HashMap)ois.readObject();
840                                 ois.close();
841                             }
842                         } catch (IOException JavaDoc ioe) {
843                             if (getLogger().isDebugEnabled()) {
844                                 StringBuffer JavaDoc debugBuffer =
845                                     new StringBuffer JavaDoc(64)
846                                     .append("Exception reading attributes ")
847                                     .append(key)
848                                     .append(" in ")
849                                     .append(repositoryName);
850                                 getLogger().debug(debugBuffer.toString(), ioe);
851                             }
852                         }
853                     } else {
854                         if (getLogger().isDebugEnabled()) {
855                             StringBuffer JavaDoc debugBuffer =
856                                 new StringBuffer JavaDoc(64)
857                                 .append("Did not find a record (attributes) ")
858                                 .append(key)
859                                 .append(" in ")
860                             .append(repositoryName);
861                             getLogger().debug(debugBuffer.toString());
862                         }
863                     }
864                 } catch (SQLException sqle) {
865                     StringBuffer JavaDoc errorBuffer = new StringBuffer JavaDoc(256)
866                                                 .append("Error retrieving message")
867                                                 .append(sqle.getMessage())
868                                                 .append(sqle.getErrorCode())
869                                                 .append(sqle.getSQLState())
870                                                 .append(sqle.getNextException());
871                     getLogger().error(errorBuffer.toString());
872                 } finally {
873                     theJDBCUtil.closeJDBCResultSet(rsMessageAttr);
874                     theJDBCUtil.closeJDBCStatement(retrieveMessageAttr);
875                 }
876             }
877
878             MailImpl mc = new MailImpl();
879             mc.setAttributesRaw (attributes);
880             mc.setName(key);
881             mc.setState(rsMessage.getString(1));
882             mc.setErrorMessage(rsMessage.getString(2));
883             String JavaDoc sender = rsMessage.getString(3);
884             if (sender == null) {
885                 mc.setSender(null);
886             } else {
887                 mc.setSender(new MailAddress(sender));
888             }
889             StringTokenizer st = new StringTokenizer(rsMessage.getString(4), "\r\n", false);
890             Set recipients = new HashSet();
891             while (st.hasMoreTokens()) {
892                 recipients.add(new MailAddress(st.nextToken()));
893             }
894             mc.setRecipients(recipients);
895             mc.setRemoteHost(rsMessage.getString(5));
896             mc.setRemoteAddr(rsMessage.getString(6));
897             mc.setLastUpdated(rsMessage.getTimestamp(7));
898
899             MimeMessageJDBCSource source = new MimeMessageJDBCSource(this, key, sr);
900             MimeMessageWrapper message = new MimeMessageWrapper(source);
901             mc.setMessage(message);
902             return mc;
903         } catch (SQLException sqle) {
904             StringBuffer JavaDoc errorBuffer = new StringBuffer JavaDoc(256)
905                                         .append("Error retrieving message")
906                                         .append(sqle.getMessage())
907                                         .append(sqle.getErrorCode())
908                                         .append(sqle.getSQLState())
909                                         .append(sqle.getNextException());
910             getLogger().error(errorBuffer.toString());
911             throw new MessagingException JavaDoc("Exception while retrieving mail: " + sqle.getMessage());
912         } catch (Exception JavaDoc me) {
913             throw new MessagingException JavaDoc("Exception while retrieving mail: " + me.getMessage());
914         } finally {
915             theJDBCUtil.closeJDBCResultSet(rsMessage);
916             theJDBCUtil.closeJDBCStatement(retrieveMessage);
917             theJDBCUtil.closeJDBCConnection(conn);
918         }
919     }
920
921     /**
922      * Removes a specified message
923      *
924      * @param mail the message to be removed from the repository
925      */

926     public void remove(MailImpl mail) throws MessagingException JavaDoc {
927         remove(mail.getName());
928     }
929
930     /**
931      * Removes a Collection of mails from the repository
932      * @param mails The Collection of <code>MailImpl</code>'s to delete
933      * @throws MessagingException
934      * @since 2.2.0
935      */

936     public void remove(Collection mails) throws MessagingException JavaDoc {
937         Iterator delList = mails.iterator();
938         while (delList.hasNext()) {
939             remove((MailImpl)delList.next());
940         }
941     }
942
943     /**
944      * Removes a message identified by a key.
945      *
946      * @param key the key of the message to be removed from the repository
947      */

948     public void remove(String JavaDoc key) throws MessagingException JavaDoc {
949         //System.err.println("removing " + key);
950
if (lock(key)) {
951             Connection conn = null;
952             PreparedStatement removeMessage = null;
953             try {
954                 conn = datasource.getConnection();
955                 removeMessage =
956                     conn.prepareStatement(sqlQueries.getSqlString("removeMessageSQL", true));
957                 removeMessage.setString(1, key);
958                 removeMessage.setString(2, repositoryName);
959                 removeMessage.execute();
960
961                 if (sr != null) {
962                     sr.remove(key);
963                 }
964             } catch (Exception JavaDoc me) {
965                 throw new MessagingException JavaDoc("Exception while removing mail: " + me.getMessage());
966             } finally {
967                 theJDBCUtil.closeJDBCStatement(removeMessage);
968                 theJDBCUtil.closeJDBCConnection(conn);
969                 unlock(key);
970             }
971         }
972     }
973
974     /**
975      * Gets a list of message keys stored in this repository.
976      *
977      * @return an Iterator of the message keys
978      */

979     public Iterator list() throws MessagingException JavaDoc {
980         //System.err.println("listing messages");
981
Connection conn = null;
982         PreparedStatement listMessages = null;
983         ResultSet rsListMessages = null;
984         try {
985             conn = datasource.getConnection();
986             listMessages =
987                 conn.prepareStatement(sqlQueries.getSqlString("listMessagesSQL", true));
988             listMessages.setString(1, repositoryName);
989             rsListMessages = listMessages.executeQuery();
990
991             List messageList = new ArrayList();
992             while (rsListMessages.next() && !Thread.currentThread().isInterrupted()) {
993                 messageList.add(rsListMessages.getString(1));
994             }
995             return messageList.iterator();
996         } catch (Exception JavaDoc me) {
997             throw new MessagingException JavaDoc("Exception while listing mail: " + me.getMessage());
998         } finally {
999             theJDBCUtil.closeJDBCResultSet(rsListMessages);
1000            theJDBCUtil.closeJDBCStatement(listMessages);
1001            theJDBCUtil.closeJDBCConnection(conn);
1002        }
1003    }
1004
1005    /**
1006     * Gets the SQL connection to be used by this JDBCMailRepository
1007     *
1008     * @return the connection
1009     * @throws SQLException if there is an issue with getting the connection
1010     */

1011    protected Connection getConnection() throws SQLException {
1012        return datasource.getConnection();
1013    }
1014
1015    /**
1016     * @see java.lang.Object#equals(Object)
1017     */

1018    public boolean equals(Object JavaDoc obj) {
1019        if (!(obj instanceof JDBCMailRepository)) {
1020            return false;
1021        }
1022        // TODO: Figure out whether other instance variables should be part of
1023
// the equals equation
1024
JDBCMailRepository repository = (JDBCMailRepository)obj;
1025        return ((repository.tableName == tableName) || ((repository.tableName != null) && repository.tableName.equals(tableName))) &&
1026                ((repository.repositoryName == repositoryName) || ((repository.repositoryName != null) && repository.repositoryName.equals(repositoryName)));
1027    }
1028
1029    /**
1030     * Provide a hash code that is consistent with equals for this class
1031     *
1032     * @return the hash code
1033     */

1034     public int hashCode() {
1035        int result = 17;
1036        if (tableName != null) {
1037            result = 37 * tableName.hashCode();
1038        }
1039        if (repositoryName != null) {
1040            result = 37 * repositoryName.hashCode();
1041        }
1042        return result;
1043     }
1044
1045    /**
1046     * This method calculates number of parameters in a prepared statement SQL String.
1047     * It does so by counting the number of '?' in the string
1048     * @param sqlstring to return parameter count for
1049     * @return number of parameters
1050     **/

1051    private int getNumberOfParameters (String JavaDoc sqlstring) {
1052        //it is alas a java 1.4 feature to be able to call
1053
//getParameterMetaData which could provide us with the parameterCount
1054
char[] chars = sqlstring.toCharArray();
1055        int count = 0;
1056        for (int i = 0; i < chars.length; i++) {
1057            count += chars[i]=='?' ? 1 : 0;
1058        }
1059        return count;
1060    }
1061
1062    /**
1063     * Closes output streams used to update message
1064     *
1065     * @param headerStream the stream containing header information - potentially the same
1066     * as the body stream
1067     * @param bodyStream the stream containing body information
1068     */

1069    private void closeOutputStreams(OutputStream JavaDoc headerStream, OutputStream JavaDoc bodyStream) {
1070        try {
1071            // If the header stream is not the same as the body stream,
1072
// close the header stream here.
1073
if ((headerStream != null) && (headerStream != bodyStream)) {
1074                headerStream.close();
1075            }
1076        } catch (IOException JavaDoc ioe) {
1077            getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.");
1078        }
1079        try {
1080            if (bodyStream != null) {
1081                bodyStream.close();
1082            }
1083        } catch (IOException JavaDoc ioe) {
1084            getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.");
1085        }
1086    }
1087}
1088
Popular Tags