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