View Javadoc

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