KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > reading > DatabaseReader


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.reading;
17
18 import java.io.BufferedInputStream JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.sql.Connection JavaDoc;
22 import java.sql.PreparedStatement JavaDoc;
23 import java.sql.ResultSet JavaDoc;
24 import java.sql.SQLException JavaDoc;
25 import java.sql.Timestamp JavaDoc;
26 import java.util.Map JavaDoc;
27
28 import org.apache.avalon.excalibur.datasource.DataSourceComponent;
29 import org.apache.avalon.framework.activity.Disposable;
30 import org.apache.avalon.framework.configuration.Configurable;
31 import org.apache.avalon.framework.configuration.Configuration;
32 import org.apache.avalon.framework.configuration.ConfigurationException;
33 import org.apache.avalon.framework.parameters.Parameters;
34 import org.apache.avalon.framework.service.ServiceException;
35 import org.apache.avalon.framework.service.ServiceManager;
36 import org.apache.avalon.framework.service.ServiceSelector;
37 import org.apache.cocoon.ProcessingException;
38 import org.apache.cocoon.ResourceNotFoundException;
39 import org.apache.cocoon.caching.CacheableProcessingComponent;
40 import org.apache.cocoon.environment.ObjectModelHelper;
41 import org.apache.cocoon.environment.Request;
42 import org.apache.cocoon.environment.Response;
43 import org.apache.cocoon.environment.SourceResolver;
44 import org.apache.excalibur.source.SourceValidity;
45 import org.apache.excalibur.source.impl.validity.NOPValidity;
46 import org.apache.excalibur.source.impl.validity.TimeStampValidity;
47 import org.xml.sax.SAXException JavaDoc;
48
49 /**
50  * This Reader pulls a resource from a database. It is configured with
51  * the Connection to use, parameters specify the table and column
52  * to pull the image from, and source specifies the source key information.
53  *
54  * @author <a HREF="mailto:bloritsch@apache.org">Berin Loritsch</a>
55  * @version CVS $Id: DatabaseReader.java 124610 2005-01-08 02:53:56Z antonio $
56  */

57 public class DatabaseReader extends ServiceableReader
58     implements Configurable, Disposable, CacheableProcessingComponent
59 {
60
61     private ServiceSelector dbselector;
62     private String JavaDoc dsn;
63     private long lastModified = System.currentTimeMillis();
64     private InputStream JavaDoc resource = null; // because HSQL doesn't yet implement getBlob()
65
private Connection JavaDoc con = null;
66     private DataSourceComponent datasource = null;
67     private String JavaDoc mimeType = null;
68     private int typeColumn = 0;
69     private boolean doCommit = false;
70     private boolean defaultCache = true;
71
72     public void service(final ServiceManager manager) throws ServiceException {
73         super.service(manager);
74         this.dbselector = (ServiceSelector) manager.lookup(DataSourceComponent.ROLE + "Selector");
75     }
76
77     /**
78      * Configure the <code>Reader</code> so that we can use the same database
79      * for all instances.
80      */

81     public void configure(Configuration conf) throws ConfigurationException {
82         this.dsn = conf.getChild("use-connection").getValue();
83         this.defaultCache = conf.getChild("invalidate").getValue("never").equals("always");
84     }
85
86     /**
87      * Set the <code>SourceResolver</code> the object model <code>Map</code>,
88      * the source and sitemap <code>Parameters</code> used to process the request.
89      */

90     public void setup(SourceResolver resolver, Map JavaDoc objectModel, String JavaDoc src, Parameters par)
91         throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
92         super.setup(resolver, objectModel, src, par);
93
94         PreparedStatement JavaDoc statement = null;
95         try {
96             this.datasource = (DataSourceComponent) dbselector.select(dsn);
97             this.con = datasource.getConnection();
98
99             if (this.con.getAutoCommit()) {
100                 this.con.setAutoCommit(false);
101             }
102
103             statement = con.prepareStatement(getQuery());
104             statement.setString(1, this.source);
105             ResultSet JavaDoc set = statement.executeQuery();
106             if (!set.next()) throw new ResourceNotFoundException("There is no resource with that key");
107
108             Response response = ObjectModelHelper.getResponse(objectModel);
109             Request request = ObjectModelHelper.getRequest(objectModel);
110
111             if (this.modifiedSince(set, request, response)) {
112                 this.resource = set.getBinaryStream(1);
113                 if (this.typeColumn != 0) {
114                     this.mimeType = set.getString(this.typeColumn);
115                 }
116
117                 if (this.resource == null) {
118                     throw new ResourceNotFoundException("There is no resource with that key");
119                 }
120             }
121
122             this.doCommit = true;
123         } catch (Exception JavaDoc e) {
124             this.doCommit = false;
125             throw new ResourceNotFoundException("DatabaseReader error:", e);
126         } finally {
127             try {
128                 if (statement != null) {
129                     statement.close();
130                 }
131             } catch(SQLException JavaDoc sqle) {
132                 throw new ResourceNotFoundException("Cannot close connection", sqle);
133             }
134         }
135     }
136
137     /**
138      * Generates the resource we need to retrieve verbatim from the
139      * database. Granted, this can be used for any resource from a
140      * database, so we may want to get rid of the bias toward images.
141      * This reader requires a number of parameters:
142      *
143      * <pre>
144      * &lt;parameter name="table" value="database_table_name"/&gt;
145      * &lt;parameter name="image" value="database_resource_column_name"/&gt;
146      * &lt;parameter name="key" value="database_lookup_key_column_name"/&gt;
147      * </pre>
148      *
149      * Please note that if any of those parameters are missing, this
150      * <code>Reader</code> cannot function. There are a number of other
151      * parameters that allow you to provide hints for the reader to
152      * optimize resource use:
153      *
154      * <pre>
155      * &lt;parameter name="last-modified" value="database_timestamp_column_name"/&gt;
156      * &lt;parameter name="content-type" value="content_mime_type"/&gt;
157      * &lt;parameter name="type-column" value="database_content_mime_type_column"/&gt;
158      * &lt;parameter name="expires" value="number_of_millis_before_refresh"/&gt;
159      * &lt;parameter name="where" value="alternate_key = 'foo'"/&gt;
160      * &lt;parameter name="order-by" value="alternate_key DESC"/&gt;
161      * </pre>
162      *
163      * Lastly, the <code>key</code> value is derived from the value of
164      * the <code>source</code> string.
165      */

166     public void generate() throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
167         try {
168             Response response = ObjectModelHelper.getResponse(objectModel);
169             this.serialize(response);
170         } catch (IOException JavaDoc ioe) {
171             getLogger().warn("Assuming client reset stream");
172
173             this.doCommit = false;
174         } catch (Exception JavaDoc e) {
175             this.doCommit = false;
176
177             throw new ResourceNotFoundException("DatabaseReader error:", e);
178         }
179     }
180
181     /**
182      * This method builds the query string used for accessing the database.
183      * If the required parameters do not exist, then we cannot build a
184      * correct query.
185      */

186     public String JavaDoc getQuery() throws Exception JavaDoc {
187         String JavaDoc table = this.parameters.getParameter("table", null);
188         String JavaDoc column = this.parameters.getParameter("image", null);
189         String JavaDoc key = this.parameters.getParameter("key", null);
190         String JavaDoc where = this.parameters.getParameter("where", null);
191         String JavaDoc orderBy = this.parameters.getParameter("order-by", null);
192         String JavaDoc typeColumn = this.parameters.getParameter("type-column", null);
193         int columnNo = 1;
194
195         if (table == null || column == null || key==null) {
196             throw new ProcessingException("We are missing a required parameter. Please include 'table', 'image', and 'key'");
197         }
198
199         String JavaDoc date = this.parameters.getParameter("last-modified", null);
200         StringBuffer JavaDoc query = new StringBuffer JavaDoc("SELECT ");
201
202         query.append(column);
203         columnNo++;
204
205         if (date != null) {
206             query.append(", ").append(date);
207             columnNo++;
208         }
209
210         if (null != orderBy) {
211             query.append(", ");
212
213             if (orderBy.endsWith(" DESC")) {
214                 query.append(orderBy.substring(0, orderBy.length() - 5));
215             } else {
216                 query.append(orderBy);
217             }
218             columnNo++;
219         }
220
221         if (null != typeColumn) {
222             query.append(", ").append(typeColumn);
223             this.typeColumn = columnNo;
224             columnNo++;
225         }
226
227         query.append(" FROM ").append(table);
228         query.append(" WHERE ").append(key).append(" = ?");
229
230         if (null != where) {
231             query.append(" AND ").append(where);
232         }
233
234         if (null != orderBy) {
235             query.append(" ORDER BY ").append(orderBy);
236         }
237
238         return query.toString();
239     }
240
241     /**
242      * Tests whether a resource has been modified or not. As Blobs and
243      * database columns usually do not have intrinsic dates on them (at
244      * least easily accessible), we have to have a database column that
245      * holds a date for the resource. Please note, that the database
246      * column <strong>must</strong> be a <code>Timestamp</code> column.
247      *
248      * In the absence of such a column this method <em>always</em>
249      * returns <code>true</code>. This is because databases are much
250      * more prone to change than filesystems, and don't have intrinsic
251      * timestamps on column updates.
252      */

253     public boolean modifiedSince(ResultSet JavaDoc set, Request request, Response response)
254     throws SQLException JavaDoc {
255         String JavaDoc lastModified = this.parameters.getParameter("last-modified", null);
256
257         if (lastModified != null) {
258             Timestamp JavaDoc modified = set.getTimestamp(lastModified, null);
259             if (null != modified) {
260                 this.lastModified = modified.getTime();
261             } else {
262                 // assume it has never been modified
263
}
264
265             response.setDateHeader("Last-Modified", this.lastModified);
266
267             return this.lastModified > request.getDateHeader("if-modified-since");
268         }
269
270         // if we have nothing to compare to, then we must assume it
271
// has been modified
272
return true;
273     }
274
275     /**
276      * This method actually performs the serialization.
277      */

278     public void serialize(Response response)
279     throws IOException JavaDoc, SQLException JavaDoc {
280         if (this.resource == null) {
281             throw new SQLException JavaDoc("The Blob is empty!");
282         }
283
284         InputStream JavaDoc is = new BufferedInputStream JavaDoc(this.resource);
285
286         long expires = parameters.getParameterAsInteger("expires", -1);
287
288         if (expires > 0) {
289             response.setDateHeader("Expires", System.currentTimeMillis() + expires);
290         }
291
292         response.setHeader("Accept-Ranges", "bytes");
293
294         byte[] buffer = new byte[8192];
295         int length = -1;
296
297         while ((length = is.read(buffer)) > -1) {
298             out.write(buffer, 0, length);
299         }
300         is.close();
301         out.flush();
302     }
303
304     /**
305      * Generate the unique key.
306      * This key must be unique inside the space of this component.
307      *
308      * @return The generated key hashes the src
309      */

310     public java.io.Serializable JavaDoc getKey() {
311         return this.source;
312     }
313
314     /**
315      * Generate the validity object.
316      *
317      * @return The generated validity object or <code>null</code> if the
318      * component is currently not cacheable.
319      */

320     public SourceValidity getValidity() {
321         if (this.lastModified > 0) {
322             return new TimeStampValidity(this.lastModified);
323         } else {
324             if (this.defaultCache) {
325                 return NOPValidity.SHARED_INSTANCE;
326             } else {
327                 return null;
328             }
329         }
330     }
331
332     public void recycle() {
333         super.recycle();
334         this.resource = null;
335         this.lastModified = 0;
336         this.mimeType = null;
337         this.typeColumn = 0;
338
339         if (this.con != null) {
340             try {
341                 if (this.doCommit) {
342                     this.con.commit();
343                 } else {
344                     this.con.rollback();
345                 }
346             } catch (SQLException JavaDoc se) {
347                 getLogger().warn("Could not commit or rollback connection", se);
348             }
349
350             try {
351                 this.con.close();
352             } catch (SQLException JavaDoc se) {
353                 getLogger().warn("Could not close connection", se);
354             }
355
356             this.con = null;
357         }
358
359         if (this.datasource != null) {
360             this.dbselector.release(this.datasource);
361             this.datasource = null;
362         }
363     }
364
365     /**
366      * dispose()
367      */

368     public void dispose()
369     {
370         this.manager.release(this.dbselector);
371     }
372
373     public String JavaDoc getMimeType() {
374         return (this.mimeType != null ?
375                 this.mimeType :
376                 this.parameters.getParameter("content-type", super.getMimeType()));
377     }
378
379 }
380
Popular Tags