001 /****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one *
003 * or more contributor license agreements. See the NOTICE file *
004 * distributed with this work for additional information *
005 * regarding copyright ownership. The ASF licenses this file *
006 * to you under the Apache License, Version 2.0 (the *
007 * "License"); you may not use this file except in compliance *
008 * with the License. You may obtain a copy of the License at *
009 * *
010 * http://www.apache.org/licenses/LICENSE-2.0 *
011 * *
012 * Unless required by applicable law or agreed to in writing, *
013 * software distributed under the License is distributed on an *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015 * KIND, either express or implied. See the License for the *
016 * specific language governing permissions and limitations *
017 * under the License. *
018 ****************************************************************/
019
020 package org.apache.james.mailrepository.jdbc;
021
022 import org.apache.commons.configuration.ConfigurationException;
023 import org.apache.commons.configuration.DefaultConfigurationBuilder;
024 import org.apache.commons.configuration.HierarchicalConfiguration;
025 import org.apache.james.core.MailImpl;
026 import org.apache.james.core.MimeMessageCopyOnWriteProxy;
027 import org.apache.james.core.MimeMessageWrapper;
028 import org.apache.james.filesystem.api.FileSystem;
029 import org.apache.james.mailrepository.lib.AbstractMailRepository;
030 import org.apache.james.repository.file.FilePersistentStreamRepository;
031 import org.apache.james.util.sql.JDBCUtil;
032 import org.apache.james.util.sql.SqlResources;
033 import org.apache.mailet.Mail;
034 import org.apache.mailet.MailAddress;
035
036 import javax.annotation.PostConstruct;
037 import javax.annotation.Resource;
038 import javax.mail.MessagingException;
039 import javax.mail.internet.MimeMessage;
040 import javax.sql.DataSource;
041
042 import java.io.ByteArrayInputStream;
043 import java.io.ByteArrayOutputStream;
044 import java.io.IOException;
045 import java.io.InputStream;
046 import java.io.ObjectOutputStream;
047 import java.io.ObjectInputStream;
048 import java.sql.Blob;
049 import java.sql.Connection;
050 import java.sql.DatabaseMetaData;
051 import java.sql.PreparedStatement;
052 import java.sql.ResultSet;
053 import java.sql.SQLException;
054 import java.sql.Statement;
055 import java.util.ArrayList;
056 import java.util.HashMap;
057 import java.util.HashSet;
058 import java.util.Iterator;
059 import java.util.List;
060 import java.util.Map;
061 import java.util.Set;
062 import java.util.StringTokenizer;
063
064 /**
065 * Implementation of a MailRepository on a database.
066 *
067 * <p>
068 * Requires a configuration element in the .conf.xml file of the form:
069 *
070 * <pre>
071 * <repository destinationURL="db://<datasource>/<table_name>/<repository_name>"
072 * type="MAIL"
073 * model="SYNCHRONOUS"/>
074 * </repository>
075 * </pre>
076 *
077 * </p>
078 * <p>
079 * destinationURL specifies..(Serge??) <br>
080 * Type can be SPOOL or MAIL <br>
081 * Model is currently not used and may be dropped
082 * </p>
083 *
084 * <p>
085 * Requires a logger called MailRepository.
086 * </p>
087 *
088 * @version CVS $Revision: 1088657 $ $Date: 2010-12-29 21:47:46 +0100 (Wed, 29
089 * Dec 2010) $
090 */
091 public class JDBCMailRepository extends AbstractMailRepository {
092
093 /**
094 * The table name parsed from the destination URL
095 */
096 protected String tableName;
097
098 /**
099 * The repository name parsed from the destination URL
100 */
101 protected String repositoryName;
102
103 /**
104 * The name of the SQL configuration file to be used to configure this
105 * repository.
106 */
107 private String sqlFileName;
108
109 /**
110 * The stream repository used in dbfile mode
111 */
112 private FilePersistentStreamRepository sr = null;
113
114 /**
115 * The JDBC datasource that provides the JDBC connection
116 */
117 protected DataSource datasource;
118
119 /**
120 * The name of the datasource used by this repository
121 */
122 protected String datasourceName;
123
124 /**
125 * Contains all of the sql strings for this component.
126 */
127 protected SqlResources sqlQueries;
128
129 /**
130 * The JDBCUtil helper class
131 */
132 protected JDBCUtil theJDBCUtil;
133
134 /**
135 * "Support for Mail Attributes under JDBC repositories is ready" indicator.
136 */
137 protected boolean jdbcMailAttributesReady = false;
138
139 /**
140 * The size threshold for in memory handling of storing operations
141 */
142 private int inMemorySizeLimit;
143
144 private FileSystem fileSystem;
145
146 private String filestore;
147
148 private String destination;
149
150 @Resource(name = "datasource")
151 public void setDatasource(DataSource datasource) {
152 this.datasource = datasource;
153 }
154
155 @Resource(name = "filesystem")
156 public void setFileSystem(FileSystem fileSystem) {
157 this.fileSystem = fileSystem;
158 }
159
160 protected void doConfigure(HierarchicalConfiguration configuration) throws ConfigurationException {
161 super.doConfigure(configuration);
162 if (getLogger().isDebugEnabled()) {
163 getLogger().debug(this.getClass().getName() + ".configure()");
164 }
165 destination = configuration.getString("[@destinationURL]");
166
167 // normalize the destination, to simplify processing.
168 if (!destination.endsWith("/")) {
169 destination += "/";
170 }
171 // Parse the DestinationURL for the name of the datasource,
172 // the table to use, and the (optional) repository Key.
173 // Split on "/", starting after "db://"
174 List<String> urlParams = new ArrayList<String>();
175 int start = 5;
176 if (destination.startsWith("dbfile")) {
177 // this is dbfile:// instead of db://
178 start += 4;
179 }
180 int end = destination.indexOf('/', start);
181 while (end > -1) {
182 urlParams.add(destination.substring(start, end));
183 start = end + 1;
184 end = destination.indexOf('/', start);
185 }
186
187 // Build SqlParameters and get datasource name from URL parameters
188 if (urlParams.size() == 0) {
189 StringBuffer exceptionBuffer = new StringBuffer(256).append("Malformed destinationURL - Must be of the format '").append("db://<data-source>[/<table>[/<repositoryName>]]'. Was passed ").append(configuration.getString("[@destinationURL]"));
190 throw new ConfigurationException(exceptionBuffer.toString());
191 }
192 if (urlParams.size() >= 1) {
193 datasourceName = (String) urlParams.get(0);
194 }
195 if (urlParams.size() >= 2) {
196 tableName = (String) urlParams.get(1);
197 }
198 if (urlParams.size() >= 3) {
199 repositoryName = "";
200 for (int i = 2; i < urlParams.size(); i++) {
201 if (i >= 3) {
202 repositoryName += '/';
203 }
204 repositoryName += (String) urlParams.get(i);
205 }
206 }
207
208 if (getLogger().isDebugEnabled()) {
209 StringBuffer logBuffer = new StringBuffer(128).append("Parsed URL: table = '").append(tableName).append("', repositoryName = '").append(repositoryName).append("'");
210 getLogger().debug(logBuffer.toString());
211 }
212
213 inMemorySizeLimit = configuration.getInt("inMemorySizeLimit", 409600000);
214
215 filestore = configuration.getString("filestore", null);
216 sqlFileName = configuration.getString("sqlFile");
217
218 }
219
220 /**
221 * Initialises the JDBC repository.
222 * <ol>
223 * <li>Tests the connection to the database.</li>
224 * <li>Loads SQL strings from the SQL definition file, choosing the
225 * appropriate SQL for this connection, and performing paramter
226 * substitution,</li>
227 * <li>Initialises the database with the required tables, if necessary.</li>
228 * </ol>
229 *
230 * @throws Exception
231 * if an error occurs
232 */
233 @PostConstruct
234 public void init() throws Exception {
235 StringBuffer logBuffer = null;
236 if (getLogger().isDebugEnabled()) {
237 getLogger().debug(this.getClass().getName() + ".initialize()");
238 }
239
240 try {
241 if (filestore != null) {
242
243 // prepare Configurations for stream repositories
244 DefaultConfigurationBuilder streamConfiguration = new DefaultConfigurationBuilder();
245
246 streamConfiguration.addProperty("[@destinationURL]", filestore);
247
248 sr = new FilePersistentStreamRepository();
249 sr.setLog(getLogger());
250 sr.setFileSystem(fileSystem);
251 sr.configure(streamConfiguration);
252 sr.init();
253
254 if (getLogger().isDebugEnabled()) {
255 getLogger().debug("Got filestore for JdbcMailRepository: " + filestore);
256 }
257 }
258
259 if (getLogger().isDebugEnabled()) {
260 StringBuffer logBuf = new StringBuffer(128).append(this.getClass().getName()).append(" created according to ").append(destination);
261 getLogger().debug(logBuf.toString());
262 }
263 } catch (Exception e) {
264 final String message = "Failed to retrieve Store component:" + e.getMessage();
265 getLogger().error(message, e);
266 throw new ConfigurationException(message, e);
267 }
268
269 theJDBCUtil = new JDBCUtil() {
270 protected void delegatedLog(String logString) {
271 JDBCMailRepository.this.getLogger().warn("JDBCMailRepository: " + logString);
272 }
273 };
274
275 // Test the connection to the database, by getting the DatabaseMetaData.
276 Connection conn = datasource.getConnection();
277 PreparedStatement createStatement = null;
278
279 try {
280 // Initialise the sql strings.
281
282 InputStream sqlFile = null;
283 try {
284 sqlFile = fileSystem.getResource(sqlFileName);
285 } catch (Exception e) {
286 getLogger().error(e.getMessage(), e);
287 throw e;
288 }
289
290 if (getLogger().isDebugEnabled()) {
291 logBuffer = new StringBuffer(128).append("Reading SQL resources from file: ").append(sqlFileName).append(", section ").append(this.getClass().getName()).append(".");
292 getLogger().debug(logBuffer.toString());
293 }
294
295 // Build the statement parameters
296 Map<String, String> sqlParameters = new HashMap<String, String>();
297 if (tableName != null) {
298 sqlParameters.put("table", tableName);
299 }
300 if (repositoryName != null) {
301 sqlParameters.put("repository", repositoryName);
302 }
303
304 sqlQueries = new SqlResources();
305 sqlQueries.init(sqlFile, this.getClass().getName(), conn, sqlParameters);
306
307 // Check if the required table exists. If not, create it.
308 DatabaseMetaData dbMetaData = conn.getMetaData();
309 // Need to ask in the case that identifiers are stored, ask the
310 // DatabaseMetaInfo.
311 // Try UPPER, lower, and MixedCase, to see if the table is there.
312 if (!(theJDBCUtil.tableExists(dbMetaData, tableName))) {
313 // Users table doesn't exist - create it.
314 createStatement = conn.prepareStatement(sqlQueries.getSqlString("createTable", true));
315 createStatement.execute();
316
317 if (getLogger().isInfoEnabled()) {
318 logBuffer = new StringBuffer(64).append("JdbcMailRepository: Created table '").append(tableName).append("'.");
319 getLogger().info(logBuffer.toString());
320 }
321 }
322
323 checkJdbcAttributesSupport(dbMetaData);
324
325 } finally {
326 theJDBCUtil.closeJDBCStatement(createStatement);
327 theJDBCUtil.closeJDBCConnection(conn);
328 }
329 }
330
331 /**
332 * Checks whether support for JDBC Mail atributes is activated for this
333 * repository and if everything is consistent.<br>
334 * Looks for both the "updateMessageAttributesSQL" and
335 * "retrieveMessageAttributesSQL" statements in sqlResources and for a table
336 * column named "message_attributes".
337 *
338 * @param dbMetaData
339 * the database metadata to be used to look up the column
340 * @throws SQLException
341 * if a fatal situation is met
342 */
343 protected void checkJdbcAttributesSupport(DatabaseMetaData dbMetaData) throws SQLException {
344 String attributesColumnName = "message_attributes";
345 boolean hasUpdateMessageAttributesSQL = false;
346 boolean hasRetrieveMessageAttributesSQL = false;
347
348 boolean hasMessageAttributesColumn = theJDBCUtil.columnExists(dbMetaData, tableName, attributesColumnName);
349
350 StringBuffer logBuffer = new StringBuffer(64).append("JdbcMailRepository '" + repositoryName + ", table '" + tableName + "': ");
351
352 // Determine whether attributes are used and available for storing
353 // Do we have updateMessageAttributesSQL?
354 String updateMessageAttrSql = sqlQueries.getSqlString("updateMessageAttributesSQL", false);
355 if (updateMessageAttrSql != null) {
356 hasUpdateMessageAttributesSQL = true;
357 }
358
359 // Determine whether attributes are used and retrieve them
360 // Do we have retrieveAttributesSQL?
361 String retrieveMessageAttrSql = sqlQueries.getSqlString("retrieveMessageAttributesSQL", false);
362 if (retrieveMessageAttrSql != null) {
363 hasRetrieveMessageAttributesSQL = true;
364 }
365
366 if (hasUpdateMessageAttributesSQL && !hasRetrieveMessageAttributesSQL) {
367 logBuffer.append("JDBC Mail Attributes support was activated for update but not for retrieval" + "(found 'updateMessageAttributesSQL' but not 'retrieveMessageAttributesSQL'" + "in table '" + tableName + "').");
368 getLogger().error(logBuffer.toString());
369 throw new SQLException(logBuffer.toString());
370 }
371 if (!hasUpdateMessageAttributesSQL && hasRetrieveMessageAttributesSQL) {
372 logBuffer.append("JDBC Mail Attributes support was activated for retrieval but not for update" + "(found 'retrieveMessageAttributesSQL' but not 'updateMessageAttributesSQL'" + "in table '" + tableName + "'.");
373 getLogger().error(logBuffer.toString());
374 throw new SQLException(logBuffer.toString());
375 }
376 if (!hasMessageAttributesColumn && (hasUpdateMessageAttributesSQL || hasRetrieveMessageAttributesSQL)) {
377 logBuffer.append("JDBC Mail Attributes support was activated but column '" + attributesColumnName + "' is missing in table '" + tableName + "'.");
378 getLogger().error(logBuffer.toString());
379 throw new SQLException(logBuffer.toString());
380 }
381 if (hasUpdateMessageAttributesSQL && hasRetrieveMessageAttributesSQL) {
382 jdbcMailAttributesReady = true;
383 if (getLogger().isInfoEnabled()) {
384 logBuffer.append("JDBC Mail Attributes support ready.");
385 getLogger().info(logBuffer.toString());
386 }
387 } else {
388 jdbcMailAttributesReady = false;
389 logBuffer.append("JDBC Mail Attributes support not activated. " + "Missing both 'updateMessageAttributesSQL' " + "and 'retrieveMessageAttributesSQL' " + "statements for table '" + tableName + "' in sqlResources.xml. " + "Will not persist in the repository '" + repositoryName + "'.");
390 getLogger().warn(logBuffer.toString());
391 }
392 }
393
394 /**
395 * @see org.apache.james.mailrepository.lib.AbstractMailRepository#internalStore(Mail)
396 */
397 protected void internalStore(Mail mc) throws IOException, MessagingException {
398 Connection conn = null;
399 try {
400 conn = datasource.getConnection();
401 // Need to determine whether need to insert this record, or update
402 // it.
403
404 // Determine whether the message body has changed, and possibly
405 // avoid
406 // updating the database.
407 boolean saveBody = false;
408
409 MimeMessage messageBody = mc.getMessage();
410 // if the message is a CopyOnWrite proxy we check the modified
411 // wrapped object.
412 if (messageBody instanceof MimeMessageCopyOnWriteProxy) {
413 MimeMessageCopyOnWriteProxy messageCow = (MimeMessageCopyOnWriteProxy) messageBody;
414 messageBody = messageCow.getWrappedMessage();
415 }
416 if (messageBody instanceof MimeMessageWrapper) {
417 MimeMessageWrapper message = (MimeMessageWrapper) messageBody;
418 saveBody = message.isModified();
419 if (saveBody) {
420 message.loadMessage();
421 }
422 } else {
423 saveBody = true;
424 }
425 MessageInputStream is = new MessageInputStream(mc, sr, inMemorySizeLimit, true);
426
427 // Begin a transaction
428 conn.setAutoCommit(false);
429
430 PreparedStatement checkMessageExists = null;
431 ResultSet rsExists = null;
432 boolean exists = false;
433 try {
434 checkMessageExists = conn.prepareStatement(sqlQueries.getSqlString("checkMessageExistsSQL", true));
435 checkMessageExists.setString(1, mc.getName());
436 checkMessageExists.setString(2, repositoryName);
437 rsExists = checkMessageExists.executeQuery();
438 exists = rsExists.next() && rsExists.getInt(1) > 0;
439 } finally {
440 theJDBCUtil.closeJDBCResultSet(rsExists);
441 theJDBCUtil.closeJDBCStatement(checkMessageExists);
442 }
443
444 if (exists) {
445 // MessageInputStream is = new
446 // MessageInputStream(mc,sr,inMemorySizeLimit, true);
447
448 // Update the existing record
449 PreparedStatement updateMessage = null;
450
451 try {
452 updateMessage = conn.prepareStatement(sqlQueries.getSqlString("updateMessageSQL", true));
453 updateMessage.setString(1, mc.getState());
454 updateMessage.setString(2, mc.getErrorMessage());
455 if (mc.getSender() == null) {
456 updateMessage.setNull(3, java.sql.Types.VARCHAR);
457 } else {
458 updateMessage.setString(3, mc.getSender().toString());
459 }
460 StringBuffer recipients = new StringBuffer();
461 for (Iterator<MailAddress> i = mc.getRecipients().iterator(); i.hasNext();) {
462 recipients.append(i.next().toString());
463 if (i.hasNext()) {
464 recipients.append("\r\n");
465 }
466 }
467 updateMessage.setString(4, recipients.toString());
468 updateMessage.setString(5, mc.getRemoteHost());
469 updateMessage.setString(6, mc.getRemoteAddr());
470 updateMessage.setTimestamp(7, new java.sql.Timestamp(mc.getLastUpdated().getTime()));
471 updateMessage.setString(8, mc.getName());
472 updateMessage.setString(9, repositoryName);
473 updateMessage.execute();
474 } finally {
475 Statement localUpdateMessage = updateMessage;
476 // Clear reference to statement
477 updateMessage = null;
478 theJDBCUtil.closeJDBCStatement(localUpdateMessage);
479 }
480
481 // Determine whether attributes are used and available for
482 // storing
483 if (jdbcMailAttributesReady && mc.hasAttributes()) {
484 String updateMessageAttrSql = sqlQueries.getSqlString("updateMessageAttributesSQL", false);
485 PreparedStatement updateMessageAttr = null;
486 try {
487 updateMessageAttr = conn.prepareStatement(updateMessageAttrSql);
488 ByteArrayOutputStream baos = new ByteArrayOutputStream();
489 ObjectOutputStream oos = new ObjectOutputStream(baos);
490 try {
491 if (mc instanceof MailImpl) {
492 oos.writeObject(((MailImpl) mc).getAttributesRaw());
493 } else {
494 HashMap temp = new HashMap();
495 for (Iterator i = mc.getAttributeNames(); i.hasNext();) {
496 String hashKey = (String) i.next();
497 temp.put(hashKey, mc.getAttribute(hashKey));
498 }
499 oos.writeObject(temp);
500 }
501 oos.flush();
502 ByteArrayInputStream attrInputStream = new ByteArrayInputStream(baos.toByteArray());
503 updateMessageAttr.setBinaryStream(1, attrInputStream, baos.size());
504 } finally {
505 try {
506 if (oos != null) {
507 oos.close();
508 }
509 } catch (IOException ioe) {
510 getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.", ioe);
511 }
512 }
513 updateMessageAttr.setString(2, mc.getName());
514 updateMessageAttr.setString(3, repositoryName);
515 updateMessageAttr.execute();
516 } catch (SQLException sqle) {
517 getLogger().info("JDBCMailRepository: Trying to update mail attributes failed.", sqle);
518
519 } finally {
520 theJDBCUtil.closeJDBCStatement(updateMessageAttr);
521 }
522 }
523
524 if (saveBody) {
525
526 PreparedStatement updateMessageBody = conn.prepareStatement(sqlQueries.getSqlString("updateMessageBodySQL", true));
527 try {
528 updateMessageBody.setBinaryStream(1, is, (int) is.getSize());
529 updateMessageBody.setString(2, mc.getName());
530 updateMessageBody.setString(3, repositoryName);
531 updateMessageBody.execute();
532
533 } finally {
534 theJDBCUtil.closeJDBCStatement(updateMessageBody);
535 }
536 }
537
538 } else {
539 // Insert the record into the database
540 PreparedStatement insertMessage = null;
541 try {
542 String insertMessageSQL = sqlQueries.getSqlString("insertMessageSQL", true);
543 int number_of_parameters = getNumberOfParameters(insertMessageSQL);
544 insertMessage = conn.prepareStatement(insertMessageSQL);
545 insertMessage.setString(1, mc.getName());
546 insertMessage.setString(2, repositoryName);
547 insertMessage.setString(3, mc.getState());
548 insertMessage.setString(4, mc.getErrorMessage());
549 if (mc.getSender() == null) {
550 insertMessage.setNull(5, java.sql.Types.VARCHAR);
551 } else {
552 insertMessage.setString(5, mc.getSender().toString());
553 }
554 StringBuffer recipients = new StringBuffer();
555 for (Iterator<MailAddress> i = mc.getRecipients().iterator(); i.hasNext();) {
556 recipients.append(i.next().toString());
557 if (i.hasNext()) {
558 recipients.append("\r\n");
559 }
560 }
561 insertMessage.setString(6, recipients.toString());
562 insertMessage.setString(7, mc.getRemoteHost());
563 insertMessage.setString(8, mc.getRemoteAddr());
564 insertMessage.setTimestamp(9, new java.sql.Timestamp(mc.getLastUpdated().getTime()));
565
566 insertMessage.setBinaryStream(10, is, (int) is.getSize());
567
568 // Store attributes
569 if (number_of_parameters > 10) {
570 ByteArrayOutputStream baos = new ByteArrayOutputStream();
571 ObjectOutputStream oos = new ObjectOutputStream(baos);
572 try {
573 if (mc instanceof MailImpl) {
574 oos.writeObject(((MailImpl) mc).getAttributesRaw());
575 } else {
576 HashMap temp = new HashMap();
577 for (Iterator i = mc.getAttributeNames(); i.hasNext();) {
578 String hashKey = (String) i.next();
579 temp.put(hashKey, mc.getAttribute(hashKey));
580 }
581 oos.writeObject(temp);
582 }
583 oos.flush();
584 ByteArrayInputStream attrInputStream = new ByteArrayInputStream(baos.toByteArray());
585 insertMessage.setBinaryStream(11, attrInputStream, baos.size());
586 } finally {
587 try {
588 if (oos != null) {
589 oos.close();
590 }
591 } catch (IOException ioe) {
592 getLogger().debug("JDBCMailRepository: Unexpected exception while closing output stream.", ioe);
593 }
594 }
595 }
596
597 insertMessage.execute();
598 } finally {
599 theJDBCUtil.closeJDBCStatement(insertMessage);
600 }
601 }
602
603 conn.commit();
604 conn.setAutoCommit(true);
605 } catch (SQLException e) {
606 getLogger().debug("Failed to store internal mail", e);
607 throw new IOException(e.getMessage());
608 } finally {
609 theJDBCUtil.closeJDBCConnection(conn);
610 }
611 }
612
613 /**
614 * @see org.apache.james.mailrepository.api.MailRepository#retrieve(String)
615 */
616 public Mail retrieve(String key) throws MessagingException {
617 if (DEEP_DEBUG) {
618 System.err.println("retrieving " + key);
619 }
620 Connection conn = null;
621 PreparedStatement retrieveMessage = null;
622 ResultSet rsMessage = null;
623 try {
624 conn = datasource.getConnection();
625 if (DEEP_DEBUG) {
626 System.err.println("got a conn " + key);
627 }
628
629 retrieveMessage = conn.prepareStatement(sqlQueries.getSqlString("retrieveMessageSQL", true));
630 retrieveMessage.setString(1, key);
631 retrieveMessage.setString(2, repositoryName);
632 rsMessage = retrieveMessage.executeQuery();
633 if (DEEP_DEBUG) {
634 System.err.println("ran the query " + key);
635 }
636 if (!rsMessage.next()) {
637 if (getLogger().isDebugEnabled()) {
638 StringBuffer debugBuffer = new StringBuffer(64).append("Did not find a record ").append(key).append(" in ").append(repositoryName);
639 getLogger().debug(debugBuffer.toString());
640 }
641 return null;
642 }
643 // Determine whether attributes are used and retrieve them
644 PreparedStatement retrieveMessageAttr = null;
645 HashMap attributes = null;
646 if (jdbcMailAttributesReady) {
647 String retrieveMessageAttrSql = sqlQueries.getSqlString("retrieveMessageAttributesSQL", false);
648 ResultSet rsMessageAttr = null;
649 try {
650 retrieveMessageAttr = conn.prepareStatement(retrieveMessageAttrSql);
651
652 retrieveMessageAttr.setString(1, key);
653 retrieveMessageAttr.setString(2, repositoryName);
654 rsMessageAttr = retrieveMessageAttr.executeQuery();
655
656 if (rsMessageAttr.next()) {
657 try {
658 byte[] serialized_attr = null;
659 String getAttributesOption = sqlQueries.getDbOption("getAttributes");
660 if (getAttributesOption != null && (getAttributesOption.equalsIgnoreCase("useBlob") || getAttributesOption.equalsIgnoreCase("useBinaryStream"))) {
661 Blob b = rsMessageAttr.getBlob(1);
662 serialized_attr = b.getBytes(1, (int) b.length());
663 } else {
664 serialized_attr = rsMessageAttr.getBytes(1);
665 }
666 // this check is for better backwards compatibility
667 if (serialized_attr != null) {
668 ByteArrayInputStream bais = new ByteArrayInputStream(serialized_attr);
669 ObjectInputStream ois = new ObjectInputStream(bais);
670 attributes = (HashMap) ois.readObject();
671 ois.close();
672 }
673 } catch (IOException ioe) {
674 if (getLogger().isDebugEnabled()) {
675 StringBuffer debugBuffer = new StringBuffer(64).append("Exception reading attributes ").append(key).append(" in ").append(repositoryName);
676 getLogger().debug(debugBuffer.toString(), ioe);
677 }
678 }
679 } else {
680 if (getLogger().isDebugEnabled()) {
681 StringBuffer debugBuffer = new StringBuffer(64).append("Did not find a record (attributes) ").append(key).append(" in ").append(repositoryName);
682 getLogger().debug(debugBuffer.toString());
683 }
684 }
685 } catch (SQLException sqle) {
686 StringBuffer errorBuffer = new StringBuffer(256).append("Error retrieving message").append(sqle.getMessage()).append(sqle.getErrorCode()).append(sqle.getSQLState()).append(sqle.getNextException());
687 getLogger().error(errorBuffer.toString());
688 } finally {
689 theJDBCUtil.closeJDBCResultSet(rsMessageAttr);
690 theJDBCUtil.closeJDBCStatement(retrieveMessageAttr);
691 }
692 }
693
694 MailImpl mc = new MailImpl();
695 mc.setAttributesRaw(attributes);
696 mc.setName(key);
697 mc.setState(rsMessage.getString(1));
698 mc.setErrorMessage(rsMessage.getString(2));
699 String sender = rsMessage.getString(3);
700 if (sender == null) {
701 mc.setSender(null);
702 } else {
703 mc.setSender(new MailAddress(sender));
704 }
705 StringTokenizer st = new StringTokenizer(rsMessage.getString(4), "\r\n", false);
706 Set recipients = new HashSet();
707 while (st.hasMoreTokens()) {
708 recipients.add(new MailAddress(st.nextToken()));
709 }
710 mc.setRecipients(recipients);
711 mc.setRemoteHost(rsMessage.getString(5));
712 mc.setRemoteAddr(rsMessage.getString(6));
713 mc.setLastUpdated(rsMessage.getTimestamp(7));
714
715 MimeMessageJDBCSource source = new MimeMessageJDBCSource(this, key, sr);
716 MimeMessageCopyOnWriteProxy message = new MimeMessageCopyOnWriteProxy(source);
717 mc.setMessage(message);
718 return mc;
719 } catch (SQLException sqle) {
720 StringBuffer errorBuffer = new StringBuffer(256).append("Error retrieving message").append(sqle.getMessage()).append(sqle.getErrorCode()).append(sqle.getSQLState()).append(sqle.getNextException());
721 getLogger().error(errorBuffer.toString());
722 getLogger().debug("Failed to retrieve mail", sqle);
723 throw new MessagingException("Exception while retrieving mail: " + sqle.getMessage(), sqle);
724 } catch (Exception me) {
725 throw new MessagingException("Exception while retrieving mail: " + me.getMessage(), me);
726 } finally {
727 theJDBCUtil.closeJDBCResultSet(rsMessage);
728 theJDBCUtil.closeJDBCStatement(retrieveMessage);
729 theJDBCUtil.closeJDBCConnection(conn);
730 }
731 }
732
733 /**
734 * @see org.apache.james.mailrepository.lib.AbstractMailRepository#internalRemove(String)
735 */
736 protected void internalRemove(String key) throws MessagingException {
737 Connection conn = null;
738 PreparedStatement removeMessage = null;
739 try {
740 conn = datasource.getConnection();
741 removeMessage = conn.prepareStatement(sqlQueries.getSqlString("removeMessageSQL", true));
742 removeMessage.setString(1, key);
743 removeMessage.setString(2, repositoryName);
744 removeMessage.execute();
745
746 if (sr != null) {
747 sr.remove(key);
748 }
749 } catch (Exception me) {
750 throw new MessagingException("Exception while removing mail: " + me.getMessage(), me);
751 } finally {
752 theJDBCUtil.closeJDBCStatement(removeMessage);
753 theJDBCUtil.closeJDBCConnection(conn);
754 }
755 }
756
757 /**
758 * @see org.apache.james.mailrepository.api.MailRepository#list()
759 */
760 public Iterator list() throws MessagingException {
761 // System.err.println("listing messages");
762 Connection conn = null;
763 PreparedStatement listMessages = null;
764 ResultSet rsListMessages = null;
765 try {
766 conn = datasource.getConnection();
767 listMessages = conn.prepareStatement(sqlQueries.getSqlString("listMessagesSQL", true));
768 listMessages.setString(1, repositoryName);
769 rsListMessages = listMessages.executeQuery();
770
771 List messageList = new ArrayList();
772 while (rsListMessages.next() && !Thread.currentThread().isInterrupted()) {
773 messageList.add(rsListMessages.getString(1));
774 }
775 return messageList.iterator();
776 } catch (Exception me) {
777 throw new MessagingException("Exception while listing mail: " + me.getMessage(), me);
778 } finally {
779 theJDBCUtil.closeJDBCResultSet(rsListMessages);
780 theJDBCUtil.closeJDBCStatement(listMessages);
781 theJDBCUtil.closeJDBCConnection(conn);
782 }
783 }
784
785 /**
786 * Gets the SQL connection to be used by this JDBCMailRepository
787 *
788 * @return the connection
789 * @throws SQLException
790 * if there is an issue with getting the connection
791 */
792 protected Connection getConnection() throws SQLException {
793 return datasource.getConnection();
794 }
795
796 /**
797 * @see java.lang.Object#equals(Object)
798 */
799 public boolean equals(Object obj) {
800 if (!(obj instanceof JDBCMailRepository)) {
801 return false;
802 }
803 // TODO: Figure out whether other instance variables should be part of
804 // the equals equation
805 JDBCMailRepository repository = (JDBCMailRepository) obj;
806 return ((repository.tableName == tableName) || ((repository.tableName != null) && repository.tableName.equals(tableName))) && ((repository.repositoryName == repositoryName) || ((repository.repositoryName != null) && repository.repositoryName.equals(repositoryName)));
807 }
808
809 /**
810 * Provide a hash code that is consistent with equals for this class
811 *
812 * @return the hash code
813 */
814 public int hashCode() {
815 int result = 17;
816 if (tableName != null) {
817 result = 37 * tableName.hashCode();
818 }
819 if (repositoryName != null) {
820 result = 37 * repositoryName.hashCode();
821 }
822 return result;
823 }
824
825 /**
826 * This method calculates number of parameters in a prepared statement SQL
827 * String. It does so by counting the number of '?' in the string
828 *
829 * @param sqlstring
830 * to return parameter count for
831 * @return number of parameters
832 **/
833 private int getNumberOfParameters(String sqlstring) {
834 // it is alas a java 1.4 feature to be able to call
835 // getParameterMetaData which could provide us with the parameterCount
836 char[] chars = sqlstring.toCharArray();
837 int count = 0;
838 for (int i = 0; i < chars.length; i++) {
839 count += chars[i] == '?' ? 1 : 0;
840 }
841 return count;
842 }
843 }