View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 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.store.StreamRepository;
21  import org.apache.james.core.MimeMessageSource;
22  import org.apache.james.util.JDBCUtil;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.SequenceInputStream;
28  import java.sql.Blob;
29  import java.sql.Connection;
30  import java.sql.PreparedStatement;
31  import java.sql.ResultSet;
32  import java.sql.SQLException;
33  
34  /***
35   * This class points to a specific message in a repository.  This will return an
36   * InputStream to the JDBC field/record, possibly sequenced with the file stream.
37   */
38  public class MimeMessageJDBCSource extends MimeMessageSource {
39  
40      /***
41       * Whether 'deep debugging' is turned on.
42       */
43      private static final boolean DEEP_DEBUG = false;
44  
45      //Define how to get to the data
46      JDBCMailRepository repository = null;
47      String key = null;
48      StreamRepository sr = null;
49  
50      private long size = -1;
51  
52      /***
53       * SQL used to retrieve the message body
54       */
55      String retrieveMessageBodySQL = null;
56  
57      /***
58       * SQL used to retrieve the size of the message body
59       */
60      String retrieveMessageBodySizeSQL = null;
61  
62      /***
63       * The JDBCUtil helper class
64       */
65      private static final JDBCUtil theJDBCUtil =
66              new JDBCUtil() {
67                  protected void delegatedLog(String logString) {
68                      // No logging available at this point in the code.
69                      // Therefore this is a noop method.
70                  }
71              };
72  
73      /***
74       * Construct a MimeMessageSource based on a JDBC repository, a key, and a
75       * stream repository (where we might store the message body)
76       */
77      public MimeMessageJDBCSource(JDBCMailRepository repository,
78              String key, StreamRepository sr) throws IOException {
79          if (repository == null) {
80              throw new IOException("Repository is null");
81          }
82          if (key == null) {
83              throw new IOException("Message name (key) was not defined");
84          }
85          this.repository = repository;
86          this.key = key;
87          this.sr = sr;
88  
89          retrieveMessageBodySQL =
90              repository.sqlQueries.getSqlString("retrieveMessageBodySQL", true);
91          // this is optional
92          retrieveMessageBodySizeSQL =
93              repository.sqlQueries.getSqlString("retrieveMessageBodySizeSQL");
94      }
95  
96      /***
97       * Returns a unique String ID that represents the location from where 
98       * this source is loaded.  This will be used to identify where the data 
99       * is, primarily to avoid situations where this data would get overwritten.
100      *
101      * @return the String ID
102      */
103     public String getSourceId() {
104         StringBuffer sourceIdBuffer =
105             new StringBuffer(128)
106                     .append(repository.repositoryName)
107                     .append("/")
108                     .append(key);
109         return sourceIdBuffer.toString();
110     }
111 
112     /***
113      * Return the input stream to the database field and then the file stream.  This should
114      * be smart enough to work even if the file does not exist.  This is to support
115      * a repository with the entire message in the database, which is how James 1.2 worked.
116      */
117     public synchronized InputStream getInputStream() throws IOException {
118         Connection conn = null;
119         PreparedStatement retrieveMessageStream = null;
120         ResultSet rsRetrieveMessageStream = null;
121         try {
122             conn = repository.getConnection();
123 
124             byte[] headers = null;
125 
126             long start = 0;
127             if (DEEP_DEBUG) {
128                 start = System.currentTimeMillis();
129                 System.out.println("starting");
130             }
131             retrieveMessageStream = conn.prepareStatement(retrieveMessageBodySQL);
132             retrieveMessageStream.setString(1, key);
133             retrieveMessageStream.setString(2, repository.repositoryName);
134             rsRetrieveMessageStream = retrieveMessageStream.executeQuery();
135 
136             if (!rsRetrieveMessageStream.next()) {
137                 throw new IOException("Could not find message");
138             }
139 
140             String getBodyOption = repository.sqlQueries.getDbOption("getBody");
141             if (getBodyOption != null && getBodyOption.equalsIgnoreCase("useBlob")) {
142                 Blob b = rsRetrieveMessageStream.getBlob(1);
143                 headers = b.getBytes(1, (int)b.length());
144             } else {
145                 headers = rsRetrieveMessageStream.getBytes(1);
146             }
147             if (DEEP_DEBUG) {
148                 System.err.println("stopping");
149                 System.err.println(System.currentTimeMillis() - start);
150             }
151 
152             InputStream in = new ByteArrayInputStream(headers);
153             try {
154                 if (sr != null) {
155                     in = new SequenceInputStream(in, sr.get(key));
156                 }
157             } catch (Exception e) {
158                 //ignore this... either sr is null, or the file does not exist
159                 // or something else
160             }
161             return in;
162         } catch (SQLException sqle) {
163             throw new IOException(sqle.toString());
164         } finally {
165             theJDBCUtil.closeJDBCResultSet(rsRetrieveMessageStream);
166             theJDBCUtil.closeJDBCStatement(retrieveMessageStream);
167             theJDBCUtil.closeJDBCConnection(conn);
168         }
169     }
170 
171     /***
172      * Runs a custom SQL statement to check the size of the message body
173      */
174     public synchronized long getMessageSize() throws IOException {
175         if (size != -1) return size;
176         if (retrieveMessageBodySizeSQL == null) {
177             //There was no SQL statement for this repository... figure it out the hard way
178             System.err.println("no SQL statement to find size");
179             return size = super.getMessageSize();
180         }
181         Connection conn = null;
182         PreparedStatement retrieveMessageSize = null;
183         ResultSet rsRetrieveMessageSize = null;
184         try {
185             conn = repository.getConnection();
186 
187             retrieveMessageSize = conn.prepareStatement(retrieveMessageBodySizeSQL);
188             retrieveMessageSize.setString(1, key);
189             retrieveMessageSize.setString(2, repository.repositoryName);
190             rsRetrieveMessageSize = retrieveMessageSize.executeQuery();
191 
192             if (!rsRetrieveMessageSize.next()) {
193                 throw new IOException("Could not find message");
194             }
195 
196             size = rsRetrieveMessageSize.getLong(1);
197 
198             InputStream in = null;
199             try {
200                 if (sr != null) {
201                     if (sr instanceof org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository) {
202                         size += ((org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository) sr).getSize(key);
203                     } else {
204                         in = sr.get(key);
205                         int len = 0;
206                         byte[] block = new byte[1024];
207                         while ((len = in.read(block)) > -1) {
208                             size += len;
209                         }
210                     }
211                 }
212             } catch (Exception e) {
213                 //ignore this... either sr is null, or the file does not exist
214                 // or something else
215             } finally {
216                 try {
217                     if (in != null) {
218                         in.close();
219                     }
220                 } catch (IOException ioe) {
221                     // Ignored - no access to logger at this point in the code
222                 }
223             }
224             
225             return size;
226         } catch (SQLException sqle) {
227             throw new IOException(sqle.toString());
228         } finally {
229             theJDBCUtil.closeJDBCResultSet(rsRetrieveMessageSize);
230             theJDBCUtil.closeJDBCStatement(retrieveMessageSize);
231             theJDBCUtil.closeJDBCConnection(conn);
232         }
233     }
234 
235     /***
236      * Check to see whether this is the same repository and the same key
237      */
238     public boolean equals(Object obj) {
239         if (obj instanceof MimeMessageJDBCSource) {
240             // TODO: Figure out whether other instance variables should be part of
241             // the equals equation
242             MimeMessageJDBCSource source = (MimeMessageJDBCSource)obj;
243             return ((source.key == key) || ((source.key != null) && source.key.equals(key))) &&
244                    ((source.repository == repository) || ((source.repository != null) && source.repository.equals(repository)));
245         }
246         return false;
247     }
248 
249     /***
250      * Provide a hash code that is consistent with equals for this class
251      *
252      * @return the hash code
253      */
254      public int hashCode() {
255         int result = 17;
256         if (key != null) {
257             result = 37 * key.hashCode();
258         }
259         if (repository != null) {
260             result = 37 * repository.hashCode();
261         }
262         return result;
263      }
264 }