View Javadoc

1   package org.apache.velocity.runtime.resource.loader;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.io.BufferedInputStream;
23  import java.io.InputStream;
24  import java.sql.Connection;
25  import java.sql.PreparedStatement;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.sql.Timestamp;
29  import javax.naming.InitialContext;
30  import javax.naming.NamingException;
31  import javax.sql.DataSource;
32  import org.apache.commons.collections.ExtendedProperties;
33  import org.apache.velocity.exception.ResourceNotFoundException;
34  import org.apache.velocity.exception.VelocityException;
35  import org.apache.velocity.runtime.resource.Resource;
36  import org.apache.velocity.util.ExceptionUtils;
37  import org.apache.velocity.util.StringUtils;
38  
39  /**
40   * <P>This is a simple template file loader that loads templates
41   * from a DataSource instead of plain files.
42   *
43   * <P>It can be configured with a datasource name, a table name,
44   * id column (name), content column (the template body) and a
45   * datetime column (for last modification info).
46   * <br>
47   * <br>
48   * Example configuration snippet for velocity.properties:
49   * <br>
50   * <br>
51   * resource.loader = file, ds <br>
52   * <br>
53   * ds.resource.loader.public.name = DataSource <br>
54   * ds.resource.loader.description = Velocity DataSource Resource Loader <br>
55   * ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader <br>
56   * ds.resource.loader.resource.datasource = java:comp/env/jdbc/Velocity <br>
57   * ds.resource.loader.resource.table = tb_velocity_template <br>
58   * ds.resource.loader.resource.keycolumn = id_template <br>
59   * ds.resource.loader.resource.templatecolumn = template_definition <br>
60   * ds.resource.loader.resource.timestampcolumn = template_timestamp <br>
61   * ds.resource.loader.cache = false <br>
62   * ds.resource.loader.modificationCheckInterval = 60 <br>
63   * <br>
64   * <P>Optionally, the developer can instantiate the DataSourceResourceLoader and set the DataSource via code in
65   * a manner similar to the following:
66   * <BR>
67   * <BR>
68   * DataSourceResourceLoader ds = new DataSourceResourceLoader();<BR>
69   * ds.setDataSource(DATASOURCE);<BR>
70   * Velocity.setProperty("ds.resource.loader.instance",ds);<BR>
71   * <P> The property <code>ds.resource.loader.class</code> should be left out, otherwise all the other
72   * properties in velocity.properties would remain the same.
73   * <BR>
74   * <BR>
75   *
76   * Example WEB-INF/web.xml: <br>
77   * <br>
78   *  <resource-ref> <br>
79   *   <description>Velocity template DataSource</description> <br>
80   *   <res-ref-name>jdbc/Velocity</res-ref-name> <br>
81   *   <res-type>javax.sql.DataSource</res-type> <br>
82   *   <res-auth>Container</res-auth> <br>
83   *  </resource-ref> <br>
84   * <br>
85   *  <br>
86   * and Tomcat 4 server.xml file: <br>
87   *  [...] <br>
88   *  <Context path="/exampleVelocity" docBase="exampleVelocity" debug="0"> <br>
89   *  [...] <br>
90   *   <ResourceParams name="jdbc/Velocity"> <br>
91   *    <parameter> <br>
92   *      <name>driverClassName</name> <br>
93   *      <value>org.hsql.jdbcDriver</value> <br>
94   *    </parameter> <br>
95   *    <parameter> <br>
96   *     <name>driverName</name> <br>
97   *     <value>jdbc:HypersonicSQL:database</value> <br>
98   *    </parameter> <br>
99   *    <parameter> <br>
100  *     <name>user</name> <br>
101  *     <value>database_username</value> <br>
102  *    </parameter> <br>
103  *    <parameter> <br>
104  *     <name>password</name> <br>
105  *     <value>database_password</value> <br>
106  *    </parameter> <br>
107  *   </ResourceParams> <br>
108  *  [...] <br>
109  *  </Context> <br>
110  *  [...] <br>
111  * <br>
112  *  Example sql script:<br>
113  *  CREATE TABLE tb_velocity_template ( <br>
114  *  id_template varchar (40) NOT NULL , <br>
115  *  template_definition text (16) NOT NULL , <br>
116  *  template_timestamp datetime NOT NULL  <br>
117  *  ) <br>
118  *
119  * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
120  * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
121  * @author <a href="mailto:david.kinnvall@alertir.com">David Kinnvall</a>
122  * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
123  * @author <a href="mailto:lachiewicz@plusnet.pl">Sylwester Lachiewicz</a>
124  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
125  * @version $Id: DataSourceResourceLoader.java 687177 2008-08-19 22:00:32Z nbubna $
126  * @since 1.5
127  */
128 public class DataSourceResourceLoader extends ResourceLoader
129 {
130     private String dataSourceName;
131     private String tableName;
132     private String keyColumn;
133     private String templateColumn;
134     private String timestampColumn;
135     private InitialContext ctx;
136     private DataSource dataSource;
137 
138     /**
139      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
140      */
141     public void init(ExtendedProperties configuration)
142     {
143         dataSourceName  = StringUtils.nullTrim(configuration.getString("resource.datasource"));
144         tableName       = StringUtils.nullTrim(configuration.getString("resource.table"));
145         keyColumn       = StringUtils.nullTrim(configuration.getString("resource.keycolumn"));
146         templateColumn  = StringUtils.nullTrim(configuration.getString("resource.templatecolumn"));
147         timestampColumn = StringUtils.nullTrim(configuration.getString("resource.timestampcolumn"));
148 
149         if (dataSource != null)
150         {
151             if (log.isDebugEnabled())
152             {
153                 log.debug("DataSourceResourceLoader: using dataSource instance with table \""
154                           + tableName + "\"");
155                 log.debug("DataSourceResourceLoader: using columns \""
156                           + keyColumn + "\", \"" + templateColumn + "\" and \""
157                           + timestampColumn + "\"");
158             }
159 
160             log.trace("DataSourceResourceLoader initialized.");
161         }
162         else if (dataSourceName != null)
163         {
164             if (log.isDebugEnabled())
165             {
166                 log.debug("DataSourceResourceLoader: using \"" + dataSourceName
167                           + "\" datasource with table \"" + tableName + "\"");
168                 log.debug("DataSourceResourceLoader: using columns \""
169                           + keyColumn + "\", \"" + templateColumn + "\" and \""
170                           + timestampColumn + "\"");
171             }
172 
173             log.trace("DataSourceResourceLoader initialized.");
174         }
175         else
176         {
177             String msg = "DataSourceResourceLoader not properly initialized. No DataSource was identified.";
178             log.error(msg);
179             throw new RuntimeException(msg);
180         }
181     }
182 
183     /**
184      * Set the DataSource used by this resource loader.  Call this as an alternative to
185      * specifying the data source name via properties.
186      * @param dataSource The data source for this ResourceLoader.
187      */
188     public void setDataSource(final DataSource dataSource)
189     {
190         this.dataSource = dataSource;
191     }
192 
193     /**
194      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
195      */
196     public boolean isSourceModified(final Resource resource)
197     {
198         return (resource.getLastModified() !=
199                 readLastModified(resource, "checking timestamp"));
200     }
201 
202     /**
203      * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
204      */
205     public long getLastModified(final Resource resource)
206     {
207         return readLastModified(resource, "getting timestamp");
208     }
209 
210     /**
211      * Get an InputStream so that the Runtime can build a
212      * template with it.
213      *
214      *  @param name name of template
215      *  @return InputStream containing template
216      * @throws ResourceNotFoundException
217      */
218     public synchronized InputStream getResourceStream(final String name)
219         throws ResourceNotFoundException
220     {
221         if (org.apache.commons.lang.StringUtils.isEmpty(name))
222         {
223             throw new ResourceNotFoundException("DataSourceResourceLoader: Template name was empty or null");
224         }
225 
226         Connection conn = null;
227         ResultSet rs = null;
228         try
229         {
230             conn = openDbConnection();
231             rs = readData(conn, templateColumn, name);
232 
233             if (rs.next())
234             {
235                 InputStream stream = rs.getBinaryStream(templateColumn);
236                 if (stream == null)
237                 {
238                     throw new ResourceNotFoundException("DataSourceResourceLoader: "
239                                                         + "template column for '"
240                                                         + name + "' is null");
241                 }
242 
243                 return new BufferedInputStream(stream);
244             }
245             else
246             {
247                 throw new ResourceNotFoundException("DataSourceResourceLoader: "
248                                                     + "could not find resource '"
249                                                     + name + "'");
250 
251             }
252         }
253         catch (SQLException sqle)
254         {
255             String msg = "DataSourceResourceLoader: database problem while getting resource '"
256                          + name + "': ";
257 
258             log.error(msg, sqle);
259             throw new ResourceNotFoundException(msg);
260         }
261         catch (NamingException ne)
262         {
263             String msg = "DataSourceResourceLoader: database problem while getting resource '"
264                          + name + "': ";
265 
266             log.error(msg, ne);
267             throw new ResourceNotFoundException(msg);
268         }
269         finally
270         {
271             closeResultSet(rs);
272             closeDbConnection(conn);
273         }
274     }
275 
276     /**
277      * Fetches the last modification time of the resource
278      *
279      * @param resource Resource object we are finding timestamp of
280      * @param operation string for logging, indicating caller's intention
281      *
282      * @return timestamp as long
283      */
284     private long readLastModified(final Resource resource, final String operation)
285     {
286         long timeStamp = 0;
287 
288         /* get the template name from the resource */
289         String name = resource.getName();
290         if (name == null || name.length() == 0)
291         {
292             String msg = "DataSourceResourceLoader: Template name was empty or null";
293             log.error(msg);
294             throw new NullPointerException(msg);
295         }
296         else
297         {
298             Connection conn = null;
299             ResultSet rs = null;
300 
301             try
302             {
303                 conn = openDbConnection();
304                 rs = readData(conn, timestampColumn, name);
305 
306                 if (rs.next())
307                 {
308                     Timestamp ts = rs.getTimestamp(timestampColumn);
309                     timeStamp = ts != null ? ts.getTime() : 0;
310                 }
311                 else
312                 {
313                     String msg = "DataSourceResourceLoader: could not find resource "
314                               + name + " while " + operation;
315                     log.error(msg);
316                     throw new ResourceNotFoundException(msg);
317                 }
318             }
319             catch (SQLException sqle)
320             {
321                 String msg = "DataSourceResourceLoader: database problem while "
322                             + operation + " of '" + name + "': ";
323 
324                 log.error(msg, sqle);
325                 throw ExceptionUtils.createRuntimeException(msg, sqle);
326             }
327             catch (NamingException ne)
328             {
329                 String msg = "DataSourceResourceLoader: database problem while "
330                              + operation + " of '" + name + "': ";
331 
332                 log.error(msg, ne);
333                 throw ExceptionUtils.createRuntimeException(msg, ne);
334             }
335             finally
336             {
337                 closeResultSet(rs);
338                 closeDbConnection(conn);
339             }
340         }
341         return timeStamp;
342     }
343 
344     /**
345      * Gets connection to the datasource specified through the configuration
346      * parameters.
347      *
348      * @return connection
349      */
350     private Connection openDbConnection() throws NamingException, SQLException
351     {
352          if (dataSource != null)
353          {
354             return dataSource.getConnection();
355          }
356 
357          if (ctx == null)
358          {
359             ctx = new InitialContext();
360          }
361 
362          dataSource = (DataSource) ctx.lookup(dataSourceName);
363 
364          return dataSource.getConnection();
365      }
366 
367     /**
368      * Closes connection to the datasource
369      */
370     private void closeDbConnection(final Connection conn)
371     {
372         if (conn != null)
373         {
374             try
375             {
376                 conn.close();
377             }
378             catch (RuntimeException re)
379             {
380                 throw re;
381             }
382             catch (Exception e)
383             {
384                 String msg = "DataSourceResourceLoader: problem when closing connection";
385                 log.error(msg, e);
386                 throw new VelocityException(msg, e);
387             }
388         }
389     }
390 
391     /**
392      * Closes the result set.
393      */
394     private void closeResultSet(final ResultSet rs)
395     {
396         if (rs != null)
397         {
398             try
399             {
400                 rs.close();
401             }
402             catch (RuntimeException re)
403             {
404                 throw re;
405             }
406             catch (Exception e)
407             {
408                 String msg = "DataSourceResourceLoader: problem when closing result set";
409                 log.error(msg, e);
410                 throw new VelocityException(msg, e);
411             }
412         }
413     }
414 
415     /**
416      * Reads the data from the datasource.  It simply does the following query :
417      * <br>
418      *  SELECT <i>columnNames</i> FROM <i>tableName</i> WHERE <i>keyColumn</i>
419      *     = '<i>templateName</i>'
420      * <br>
421      * where <i>keyColumn</i> is a class member set in init()
422      *
423      * @param conn connection to datasource
424      * @param columnNames columns to fetch from datasource
425      * @param templateName name of template to fetch
426      * @return result set from query
427      */
428     private ResultSet readData(final Connection conn,
429                                final String columnNames,
430                                final String templateName) throws SQLException
431     {
432         PreparedStatement ps = conn.prepareStatement("SELECT " + columnNames + " FROM "+ tableName + " WHERE " + keyColumn + " = ?");
433         ps.setString(1, templateName);
434         return ps.executeQuery();
435     }
436 
437 }