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