001: package org.apache.velocity.runtime.resource.loader;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.BufferedInputStream;
023: import java.io.InputStream;
024: import java.sql.Connection;
025: import java.sql.PreparedStatement;
026: import java.sql.ResultSet;
027: import java.sql.SQLException;
028: import java.sql.Timestamp;
029:
030: import javax.naming.InitialContext;
031: import javax.naming.NamingException;
032: import javax.sql.DataSource;
033:
034: import org.apache.commons.collections.ExtendedProperties;
035: import org.apache.velocity.exception.ResourceNotFoundException;
036: import org.apache.velocity.runtime.resource.Resource;
037: import org.apache.velocity.util.ExceptionUtils;
038: import org.apache.velocity.util.StringUtils;
039:
040: /**
041: * <P>This is a simple template file loader that loads templates
042: * from a DataSource instead of plain files.
043: *
044: * <P>It can be configured with a datasource name, a table name,
045: * id column (name), content column (the template body) and a
046: * datetime column (for last modification info).
047: * <br>
048: * <br>
049: * Example configuration snippet for velocity.properties:
050: * <br>
051: * <br>
052: * resource.loader = file, ds <br>
053: * <br>
054: * ds.resource.loader.public.name = DataSource <br>
055: * ds.resource.loader.description = Velocity DataSource Resource Loader <br>
056: * ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader <br>
057: * ds.resource.loader.resource.datasource = java:comp/env/jdbc/Velocity <br>
058: * ds.resource.loader.resource.table = tb_velocity_template <br>
059: * ds.resource.loader.resource.keycolumn = id_template <br>
060: * ds.resource.loader.resource.templatecolumn = template_definition <br>
061: * ds.resource.loader.resource.timestampcolumn = template_timestamp <br>
062: * ds.resource.loader.cache = false <br>
063: * ds.resource.loader.modificationCheckInterval = 60 <br>
064: * <br>
065: * <P>Optionally, the developer can instantiate the DataSourceResourceLoader and set the DataSource via code in
066: * a manner similar to the following:
067: * <BR>
068: * <BR>
069: * DataSourceResourceLoader ds = new DataSourceResourceLoader();<BR>
070: * ds.setDataSource(DATASOURCE);<BR>
071: * Velocity.setProperty("ds.resource.loader.instance",ds);<BR>
072: * <P> The property <code>ds.resource.loader.class</code> should be left out, otherwise all the other
073: * properties in velocity.properties would remain the same.
074: * <BR>
075: * <BR>
076: *
077: * Example WEB-INF/web.xml: <br>
078: * <br>
079: * <resource-ref> <br>
080: * <description>Velocity template DataSource</description> <br>
081: * <res-ref-name>jdbc/Velocity</res-ref-name> <br>
082: * <res-type>javax.sql.DataSource</res-type> <br>
083: * <res-auth>Container</res-auth> <br>
084: * </resource-ref> <br>
085: * <br>
086: * <br>
087: * and Tomcat 4 server.xml file: <br>
088: * [...] <br>
089: * <Context path="/exampleVelocity" docBase="exampleVelocity" debug="0"> <br>
090: * [...] <br>
091: * <ResourceParams name="jdbc/Velocity"> <br>
092: * <parameter> <br>
093: * <name>driverClassName</name> <br>
094: * <value>org.hsql.jdbcDriver</value> <br>
095: * </parameter> <br>
096: * <parameter> <br>
097: * <name>driverName</name> <br>
098: * <value>jdbc:HypersonicSQL:database</value> <br>
099: * </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: private String dataSourceName;
130: private String tableName;
131: private String keyColumn;
132: private String templateColumn;
133: private String timestampColumn;
134: private InitialContext ctx;
135: private DataSource dataSource;
136:
137: /**
138: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
139: */
140: public void init(ExtendedProperties configuration) {
141: dataSourceName = StringUtils.nullTrim(configuration
142: .getString("resource.datasource"));
143: tableName = StringUtils.nullTrim(configuration
144: .getString("resource.table"));
145: keyColumn = StringUtils.nullTrim(configuration
146: .getString("resource.keycolumn"));
147: templateColumn = StringUtils.nullTrim(configuration
148: .getString("resource.templatecolumn"));
149: timestampColumn = StringUtils.nullTrim(configuration
150: .getString("resource.timestampcolumn"));
151:
152: if (dataSource != null) {
153: if (log.isDebugEnabled()) {
154: log
155: .debug("DataSourceResourceLoader: using dataSource instance with table \""
156: + tableName + "\"");
157: log.debug("DataSourceResourceLoader: using columns \""
158: + keyColumn + "\", \"" + templateColumn
159: + "\" and \"" + timestampColumn + "\"");
160: }
161:
162: log.trace("DataSourceResourceLoader initialized.");
163: } else if (dataSourceName != null) {
164: if (log.isDebugEnabled()) {
165: log.debug("DataSourceResourceLoader: using \""
166: + dataSourceName
167: + "\" datasource with table \"" + tableName
168: + "\"");
169: log.debug("DataSourceResourceLoader: using columns \""
170: + keyColumn + "\", \"" + templateColumn
171: + "\" and \"" + timestampColumn + "\"");
172: }
173:
174: log.trace("DataSourceResourceLoader initialized.");
175: } else {
176: log
177: .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: this .dataSource = dataSource;
188: }
189:
190: /**
191: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
192: */
193: public boolean isSourceModified(final Resource resource) {
194: return (resource.getLastModified() != readLastModified(
195: resource, "checking timestamp"));
196: }
197:
198: /**
199: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
200: */
201: public long getLastModified(final Resource resource) {
202: return readLastModified(resource, "getting timestamp");
203: }
204:
205: /**
206: * Get an InputStream so that the Runtime can build a
207: * template with it.
208: *
209: * @param name name of template
210: * @return InputStream containing template
211: * @throws ResourceNotFoundException
212: */
213: public synchronized InputStream getResourceStream(final String name)
214: throws ResourceNotFoundException {
215: if (org.apache.commons.lang.StringUtils.isEmpty(name)) {
216: throw new ResourceNotFoundException(
217: "DataSourceResourceLoader: "
218: + "Template name was empty or null");
219: }
220:
221: Connection conn = null;
222: ResultSet rs = null;
223: try {
224: conn = openDbConnection();
225: rs = readData(conn, templateColumn, name);
226:
227: if (rs.next()) {
228: InputStream ascStream = rs
229: .getAsciiStream(templateColumn);
230: if (ascStream == null) {
231: throw new ResourceNotFoundException(
232: "DataSourceResourceLoader: "
233: + "template column for '" + name
234: + "' is null");
235: }
236:
237: return new BufferedInputStream(ascStream);
238: } else {
239: throw new ResourceNotFoundException(
240: "DataSourceResourceLoader: "
241: + "could not find resource '" + name
242: + "'");
243:
244: }
245: } catch (SQLException sqle) {
246: String msg = "DataSourceResourceLoader: database problem while getting resource '"
247: + name + "': ";
248:
249: log.error(msg, sqle);
250: throw new ResourceNotFoundException(msg);
251: } catch (NamingException ne) {
252: String msg = "DataSourceResourceLoader: database problem while getting resource '"
253: + name + "': ";
254:
255: log.error(msg, ne);
256: throw new ResourceNotFoundException(msg);
257: } finally {
258: closeResultSet(rs);
259: closeDbConnection(conn);
260: }
261: }
262:
263: /**
264: * Fetches the last modification time of the resource
265: *
266: * @param resource Resource object we are finding timestamp of
267: * @param operation string for logging, indicating caller's intention
268: *
269: * @return timestamp as long
270: */
271: private long readLastModified(final Resource resource,
272: final String operation) {
273: long timeStamp = 0;
274:
275: /*
276: * get the template name from the resource
277: */
278: String name = resource.getName();
279:
280: if (name == null || name.length() == 0) {
281: log.error("DataSourceResourceLoader: "
282: + "Template name was empty or null");
283: } else {
284: Connection conn = null;
285: ResultSet rs = null;
286:
287: try {
288: conn = openDbConnection();
289: rs = readData(conn, timestampColumn, name);
290:
291: if (rs.next()) {
292: Timestamp ts = rs.getTimestamp(timestampColumn);
293: timeStamp = ts != null ? ts.getTime() : 0;
294: } else {
295: log
296: .error("DataSourceResourceLoader: could not find resource "
297: + name + " while " + operation);
298: }
299: } catch (SQLException sqle) {
300: String msg = "DataSourceResourceLoader: database problem while "
301: + operation + " of '" + name + "': ";
302:
303: log.error(msg, sqle);
304: throw ExceptionUtils.createRuntimeException(msg, sqle);
305: } catch (NamingException ne) {
306: String msg = "DataSourceResourceLoader: database problem while "
307: + operation + " of '" + name + "': ";
308:
309: log.error(msg, ne);
310: throw ExceptionUtils.createRuntimeException(msg, ne);
311: } finally {
312: closeResultSet(rs);
313: closeDbConnection(conn);
314: }
315: }
316: return timeStamp;
317: }
318:
319: /**
320: * Gets connection to the datasource specified through the configuration
321: * parameters.
322: *
323: * @return connection
324: */
325: private Connection openDbConnection() throws NamingException,
326: SQLException {
327: if (dataSource != null) {
328: return dataSource.getConnection();
329: }
330:
331: if (ctx == null) {
332: ctx = new InitialContext();
333: }
334:
335: dataSource = (DataSource) ctx.lookup(dataSourceName);
336:
337: return dataSource.getConnection();
338: }
339:
340: /**
341: * Closes connection to the datasource
342: */
343: private void closeDbConnection(final Connection conn) {
344: if (conn != null) {
345: try {
346: conn.close();
347: } catch (RuntimeException re) {
348: throw re;
349: } catch (Exception e) {
350: log
351: .warn(
352: "DataSourceResourceLoader: problem when closing connection",
353: e);
354: }
355: }
356: }
357:
358: /**
359: * Closes the result set.
360: */
361: private void closeResultSet(final ResultSet rs) {
362: if (rs != null) {
363: try {
364: rs.close();
365: } catch (RuntimeException re) {
366: throw re;
367: } catch (Exception e) {
368: log
369: .warn(
370: "DataSourceResourceLoader: problem when closing result set: ",
371: e);
372: }
373: }
374: }
375:
376: /**
377: * Reads the data from the datasource. It simply does the following query :
378: * <br>
379: * SELECT <i>columnNames</i> FROM <i>tableName</i> WHERE <i>keyColumn</i>
380: * = '<i>templateName</i>'
381: * <br>
382: * where <i>keyColumn</i> is a class member set in init()
383: *
384: * @param conn connection to datasource
385: * @param columnNames columns to fetch from datasource
386: * @param templateName name of template to fetch
387: * @return result set from query
388: */
389: private ResultSet readData(final Connection conn,
390: final String columnNames, final String templateName)
391: throws SQLException {
392: PreparedStatement ps = conn.prepareStatement("SELECT "
393: + columnNames + " FROM " + tableName + " WHERE "
394: + keyColumn + " = ?");
395: ps.setString(1, templateName);
396: return ps.executeQuery();
397: }
398: }
|