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 org.apache.james.core.MimeMessageSource; 023import org.apache.james.repository.api.StreamRepository; 024import org.apache.james.util.sql.JDBCUtil; 025 026import java.io.ByteArrayInputStream; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.SequenceInputStream; 030import java.sql.Blob; 031import java.sql.Connection; 032import java.sql.PreparedStatement; 033import java.sql.ResultSet; 034import 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 */ 041public 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 return repository.repositoryName + "/" + key; 117 } 118 119 /** 120 * Return the input stream to the database field and then the file stream. 121 * This should be smart enough to work even if the file does not exist. This 122 * is to support a repository with the entire message in the database, which 123 * is how James 1.2 worked. 124 * 125 * @see org.apache.james.core.MimeMessageSource#getInputStream() 126 */ 127 public synchronized InputStream getInputStream() throws IOException { 128 Connection conn = null; 129 PreparedStatement retrieveMessageStream = null; 130 ResultSet rsRetrieveMessageStream = null; 131 try { 132 conn = repository.getConnection(); 133 134 byte[] headers; 135 136 long start = 0; 137 if (DEEP_DEBUG) { 138 start = System.currentTimeMillis(); 139 System.out.println("starting"); 140 } 141 retrieveMessageStream = conn.prepareStatement(retrieveMessageBodySQL); 142 retrieveMessageStream.setString(1, key); 143 retrieveMessageStream.setString(2, repository.repositoryName); 144 rsRetrieveMessageStream = retrieveMessageStream.executeQuery(); 145 146 if (!rsRetrieveMessageStream.next()) { 147 throw new IOException("Could not find message"); 148 } 149 150 String getBodyOption = repository.sqlQueries.getDbOption("getBody"); 151 if (getBodyOption != null && getBodyOption.equalsIgnoreCase("useBlob")) { 152 Blob b = rsRetrieveMessageStream.getBlob(1); 153 headers = b.getBytes(1, (int) b.length()); 154 } else { 155 headers = rsRetrieveMessageStream.getBytes(1); 156 } 157 if (DEEP_DEBUG) { 158 System.err.println("stopping"); 159 System.err.println(System.currentTimeMillis() - start); 160 } 161 162 InputStream in = new ByteArrayInputStream(headers); 163 try { 164 if (sr != null) { 165 in = new SequenceInputStream(in, sr.get(key)); 166 } 167 } catch (Exception e) { 168 // ignore this... either sr is null, or the file does not exist 169 // or something else 170 } 171 return in; 172 } catch (SQLException sqle) { 173 throw new IOException(sqle.toString()); 174 } finally { 175 theJDBCUtil.closeJDBCResultSet(rsRetrieveMessageStream); 176 theJDBCUtil.closeJDBCStatement(retrieveMessageStream); 177 theJDBCUtil.closeJDBCConnection(conn); 178 } 179 } 180 181 /** 182 * Runs a custom SQL statement to check the size of the message body 183 * 184 * @see org.apache.james.core.MimeMessageSource#getMessageSize() 185 */ 186 public synchronized long getMessageSize() throws IOException { 187 if (size != -1) 188 return size; 189 if (retrieveMessageBodySizeSQL == null) { 190 // There was no SQL statement for this repository... figure it out 191 // the hard way 192 System.err.println("no SQL statement to find size"); 193 return size = super.getMessageSize(); 194 } 195 Connection conn = null; 196 PreparedStatement retrieveMessageSize = null; 197 ResultSet rsRetrieveMessageSize = null; 198 try { 199 conn = repository.getConnection(); 200 201 retrieveMessageSize = conn.prepareStatement(retrieveMessageBodySizeSQL); 202 retrieveMessageSize.setString(1, key); 203 retrieveMessageSize.setString(2, repository.repositoryName); 204 rsRetrieveMessageSize = retrieveMessageSize.executeQuery(); 205 206 if (!rsRetrieveMessageSize.next()) { 207 throw new IOException("Could not find message"); 208 } 209 210 size = rsRetrieveMessageSize.getLong(1); 211 212 InputStream in = null; 213 try { 214 if (sr != null) { 215 if (sr instanceof org.apache.james.repository.file.FilePersistentStreamRepository) { 216 size += ((org.apache.james.repository.file.FilePersistentStreamRepository) sr).getSize(key); 217 } else { 218 in = sr.get(key); 219 int len; 220 byte[] block = new byte[1024]; 221 while ((len = in.read(block)) > -1) { 222 size += len; 223 } 224 } 225 } 226 } catch (Exception e) { 227 // ignore this... either sr is null, or the file does not exist 228 // or something else 229 } finally { 230 try { 231 if (in != null) { 232 in.close(); 233 } 234 } catch (IOException ioe) { 235 // Ignored - no access to logger at this point in the code 236 } 237 } 238 239 return size; 240 } catch (SQLException sqle) { 241 throw new IOException(sqle.toString()); 242 } finally { 243 theJDBCUtil.closeJDBCResultSet(rsRetrieveMessageSize); 244 theJDBCUtil.closeJDBCStatement(retrieveMessageSize); 245 theJDBCUtil.closeJDBCConnection(conn); 246 } 247 } 248 249 /** 250 * Check to see whether this is the same repository and the same key 251 */ 252 public boolean equals(Object obj) { 253 if (obj instanceof MimeMessageJDBCSource) { 254 // TODO: Figure out whether other instance variables should be part 255 // of 256 // the equals equation 257 MimeMessageJDBCSource source = (MimeMessageJDBCSource) obj; 258 return ((source.key.equals(key)) || ((source.key != null) && source.key.equals(key))) && ((source.repository == repository) || ((source.repository != null) && source.repository.equals(repository))); 259 } 260 return false; 261 } 262 263 /** 264 * Provide a hash code that is consistent with equals for this class 265 * 266 * @return the hash code 267 */ 268 public int hashCode() { 269 int result = 17; 270 if (key != null) { 271 result = 37 * key.hashCode(); 272 } 273 if (repository != null) { 274 result = 37 * repository.hashCode(); 275 } 276 return result; 277 } 278 279}