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    }