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