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.james.core.MimeMessageSource;
023 import org.apache.james.repository.api.StreamRepository;
024 import org.apache.james.util.sql.JDBCUtil;
025
026 import java.io.ByteArrayInputStream;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.io.SequenceInputStream;
030 import java.sql.Blob;
031 import java.sql.Connection;
032 import java.sql.PreparedStatement;
033 import java.sql.ResultSet;
034 import java.sql.SQLException;
035
036 /**
037 * This class points to a specific message in a repository. This will return an
038 * InputStream to the JDBC field/record, possibly sequenced with the file
039 * stream.
040 */
041 public class MimeMessageJDBCSource extends MimeMessageSource {
042
043 /**
044 * Whether 'deep debugging' is turned on.
045 */
046 private static final boolean DEEP_DEBUG = false;
047
048 // Define how to get to the data
049 JDBCMailRepository repository = null;
050 String key = null;
051 StreamRepository sr = null;
052
053 private long size = -1;
054
055 /**
056 * SQL used to retrieve the message body
057 */
058 String retrieveMessageBodySQL = null;
059
060 /**
061 * SQL used to retrieve the size of the message body
062 */
063 String retrieveMessageBodySizeSQL = null;
064
065 /**
066 * The JDBCUtil helper class
067 */
068 private static final JDBCUtil theJDBCUtil = new JDBCUtil() {
069 protected void delegatedLog(String logString) {
070 // No logging available at this point in the code.
071 // Therefore this is a noop method.
072 }
073 };
074
075 /**
076 * Construct a MimeMessageSource based on a JDBC repository, a key, and a
077 * stream repository (where we might store the message body)
078 *
079 * @param repository
080 * the JDBCMailRepository to use
081 * @param key
082 * the key for the particular stream in the stream repository to
083 * be used by this data source.
084 * @param sr
085 * the stream repository used by this data source.
086 * @throws IOException
087 * get thrown if an IO error detected
088 */
089 public MimeMessageJDBCSource(JDBCMailRepository repository, String key, StreamRepository sr) throws IOException {
090 super();
091
092 if (repository == null) {
093 throw new IOException("Repository is null");
094 }
095 if (key == null) {
096 throw new IOException("Message name (key) was not defined");
097 }
098 this.repository = repository;
099 this.key = key;
100 this.sr = sr;
101
102 retrieveMessageBodySQL = repository.sqlQueries.getSqlString("retrieveMessageBodySQL", true);
103 // this is optional
104 retrieveMessageBodySizeSQL = repository.sqlQueries.getSqlString("retrieveMessageBodySizeSQL");
105
106 }
107
108 /**
109 * Returns a unique String ID that represents the location from where this
110 * source is loaded. This will be used to identify where the data is,
111 * primarily to avoid situations where this data would get overwritten.
112 *
113 * @return the String ID
114 */
115 public String getSourceId() {
116 StringBuffer sourceIdBuffer = new StringBuffer(128).append(repository.repositoryName).append("/").append(key);
117 return sourceIdBuffer.toString();
118 }
119
120 /**
121 * Return the input stream to the database field and then the file stream.
122 * This should be smart enough to work even if the file does not exist. This
123 * is to support a repository with the entire message in the database, which
124 * 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)
189 return size;
190 if (retrieveMessageBodySizeSQL == null) {
191 // There was no SQL statement for this repository... figure it out
192 // the hard way
193 System.err.println("no SQL statement to find size");
194 return size = super.getMessageSize();
195 }
196 Connection conn = null;
197 PreparedStatement retrieveMessageSize = null;
198 ResultSet rsRetrieveMessageSize = null;
199 try {
200 conn = repository.getConnection();
201
202 retrieveMessageSize = conn.prepareStatement(retrieveMessageBodySizeSQL);
203 retrieveMessageSize.setString(1, key);
204 retrieveMessageSize.setString(2, repository.repositoryName);
205 rsRetrieveMessageSize = retrieveMessageSize.executeQuery();
206
207 if (!rsRetrieveMessageSize.next()) {
208 throw new IOException("Could not find message");
209 }
210
211 size = rsRetrieveMessageSize.getLong(1);
212
213 InputStream in = null;
214 try {
215 if (sr != null) {
216 if (sr instanceof org.apache.james.repository.file.FilePersistentStreamRepository) {
217 size += ((org.apache.james.repository.file.FilePersistentStreamRepository) sr).getSize(key);
218 } else {
219 in = sr.get(key);
220 int len = 0;
221 byte[] block = new byte[1024];
222 while ((len = in.read(block)) > -1) {
223 size += len;
224 }
225 }
226 }
227 } catch (Exception e) {
228 // ignore this... either sr is null, or the file does not exist
229 // or something else
230 } finally {
231 try {
232 if (in != null) {
233 in.close();
234 }
235 } catch (IOException ioe) {
236 // Ignored - no access to logger at this point in the code
237 }
238 }
239
240 return size;
241 } catch (SQLException sqle) {
242 throw new IOException(sqle.toString());
243 } finally {
244 theJDBCUtil.closeJDBCResultSet(rsRetrieveMessageSize);
245 theJDBCUtil.closeJDBCStatement(retrieveMessageSize);
246 theJDBCUtil.closeJDBCConnection(conn);
247 }
248 }
249
250 /**
251 * Check to see whether this is the same repository and the same key
252 */
253 public boolean equals(Object obj) {
254 if (obj instanceof MimeMessageJDBCSource) {
255 // TODO: Figure out whether other instance variables should be part
256 // of
257 // the equals equation
258 MimeMessageJDBCSource source = (MimeMessageJDBCSource) obj;
259 return ((source.key == key) || ((source.key != null) && source.key.equals(key))) && ((source.repository == repository) || ((source.repository != null) && source.repository.equals(repository)));
260 }
261 return false;
262 }
263
264 /**
265 * Provide a hash code that is consistent with equals for this class
266 *
267 * @return the hash code
268 */
269 public int hashCode() {
270 int result = 17;
271 if (key != null) {
272 result = 37 * key.hashCode();
273 }
274 if (repository != null) {
275 result = 37 * repository.hashCode();
276 }
277 return result;
278 }
279
280 }