package org.apache.velocity.runtime.resource.loader; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.VelocityException; import org.apache.velocity.runtime.resource.Resource; import org.apache.velocity.util.ExtProperties; import org.apache.velocity.util.StringUtils; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; /** *
This is a simple template file loader that loads templates * from a DataSource instead of plain files. * *
It can be configured with a datasource name, a table name,
* id column (name), content column (the template body) and a
* datetime column (for last modification info).
*
*
* Example configuration snippet for velocity.properties:
*
*
* resource.loader = file, ds
*
* ds.resource.loader.public.name = DataSource
* ds.resource.loader.description = Velocity DataSource Resource Loader
* ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader
* ds.resource.loader.resource.datasource = java:comp/env/jdbc/Velocity
* ds.resource.loader.resource.table = tb_velocity_template
* ds.resource.loader.resource.keycolumn = id_template
* ds.resource.loader.resource.templatecolumn = template_definition
* ds.resource.loader.resource.timestampcolumn = template_timestamp
* ds.resource.loader.cache = false
* ds.resource.loader.modificationCheckInterval = 60
*
*
Optionally, the developer can instantiate the DataSourceResourceLoader and set the DataSource via code in
* a manner similar to the following:
*
*
* DataSourceResourceLoader ds = new DataSourceResourceLoader();
* ds.setDataSource(DATASOURCE);
* Velocity.setProperty("ds.resource.loader.instance",ds);
*
The property ds.resource.loader.class
should be left out, otherwise all the other
* properties in velocity.properties would remain the same.
*
*
*
* Example WEB-INF/web.xml:
*
*
*
*
*
*
*
*
*
* and Tomcat 4 server.xml file:
* [...]
*
* [...]
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* [...]
*
* [...]
*
* Example sql script:
* CREATE TABLE tb_velocity_template (
* id_template varchar (40) NOT NULL ,
* template_definition text (16) NOT NULL ,
* template_timestamp datetime NOT NULL
* )
*
* @author Will Glass-Husain
* @author Matt Raible
* @author David Kinnvall
* @author Paulo Gaspar
* @author Sylwester Lachiewicz
* @author Henning P. Schmiedehausen
* @version $Id$
* @since 1.5
*/
public class DataSourceResourceLoader extends ResourceLoader
{
private String dataSourceName;
private String tableName;
private String keyColumn;
private String templateColumn;
private String timestampColumn;
private InitialContext ctx;
private DataSource dataSource;
/**
* @see ResourceLoader#init(org.apache.velocity.util.ExtProperties)
*/
public void init(ExtProperties configuration)
{
dataSourceName = StringUtils.nullTrim(configuration.getString("resource.datasource"));
tableName = StringUtils.nullTrim(configuration.getString("resource.table"));
keyColumn = StringUtils.nullTrim(configuration.getString("resource.keycolumn"));
templateColumn = StringUtils.nullTrim(configuration.getString("resource.templatecolumn"));
timestampColumn = StringUtils.nullTrim(configuration.getString("resource.timestampcolumn"));
if (dataSource != null)
{
log.debug("DataSourceResourceLoader: using dataSource instance with table \"{}\"", tableName);
log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn);
log.trace("DataSourceResourceLoader initialized.");
}
else if (dataSourceName != null)
{
log.debug("DataSourceResourceLoader: using \"{}\" datasource with table \"{}\"", dataSourceName, tableName);
log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn);
log.trace("DataSourceResourceLoader initialized.");
}
else
{
String msg = "DataSourceResourceLoader not properly initialized. No DataSource was identified.";
log.error(msg);
throw new RuntimeException(msg);
}
}
/**
* Set the DataSource used by this resource loader. Call this as an alternative to
* specifying the data source name via properties.
* @param dataSource The data source for this ResourceLoader.
*/
public void setDataSource(final DataSource dataSource)
{
this.dataSource = dataSource;
}
/**
* @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
*/
public boolean isSourceModified(final Resource resource)
{
return (resource.getLastModified() !=
readLastModified(resource, "checking timestamp"));
}
/**
* @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
*/
public long getLastModified(final Resource resource)
{
return readLastModified(resource, "getting timestamp");
}
/**
* Get an InputStream so that the Runtime can build a
* template with it.
*
* @param name name of template
* @param encoding asked encoding
* @return InputStream containing template
* @throws ResourceNotFoundException
* @since 2.0
*/
public synchronized Reader getResourceReader(final String name, String encoding)
throws ResourceNotFoundException
{
if (org.apache.commons.lang3.StringUtils.isEmpty(name))
{
throw new ResourceNotFoundException("DataSourceResourceLoader: Template name was empty or null");
}
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
try
{
conn = openDbConnection();
ps = getStatement(conn, templateColumn, tableName, keyColumn, name);
rs = ps.executeQuery();
if (rs.next())
{
InputStream rawStream = rs.getAsciiStream(templateColumn);
if (rawStream == null)
{
throw new ResourceNotFoundException("DataSourceResourceLoader: "
+ "template column for '"
+ name + "' is null");
}
try
{
return buildReader(rawStream, encoding);
}
catch (Exception e)
{
if (rawStream != null)
{
try
{
rawStream.close();
}
catch(IOException ioe) {}
}
String msg = "Exception while loading Template column for " + name;
log.error(msg, e);
throw new VelocityException(msg, e);
}
}
else
{
throw new ResourceNotFoundException("DataSourceResourceLoader: "
+ "could not find resource '"
+ name + "'");
}
}
catch (SQLException sqle)
{
String msg = "DataSourceResourceLoader: database problem while getting resource '"
+ name + "': ";
log.error(msg, sqle);
throw new ResourceNotFoundException(msg);
}
catch (NamingException ne)
{
String msg = "DataSourceResourceLoader: database problem while getting resource '"
+ name + "': ";
log.error(msg, ne);
throw new ResourceNotFoundException(msg);
}
finally
{
closeResultSet(rs);
closeStatement(ps);
closeDbConnection(conn);
}
}
/**
* Fetches the last modification time of the resource
*
* @param resource Resource object we are finding timestamp of
* @param operation string for logging, indicating caller's intention
*
* @return timestamp as long
*/
private long readLastModified(final Resource resource, final String operation)
{
long timeStamp = 0;
/* get the template name from the resource */
String name = resource.getName();
if (name == null || name.length() == 0)
{
String msg = "DataSourceResourceLoader: Template name was empty or null";
log.error(msg);
throw new NullPointerException(msg);
}
else
{
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
try
{
conn = openDbConnection();
ps = getStatement(conn, timestampColumn, tableName, keyColumn, name);
rs = ps.executeQuery();
if (rs.next())
{
Timestamp ts = rs.getTimestamp(timestampColumn);
timeStamp = ts != null ? ts.getTime() : 0;
}
else
{
String msg = "DataSourceResourceLoader: could not find resource "
+ name + " while " + operation;
log.error(msg);
throw new ResourceNotFoundException(msg);
}
}
catch (SQLException sqle)
{
String msg = "DataSourceResourceLoader: database problem while "
+ operation + " of '" + name + "': ";
log.error(msg, sqle);
throw new VelocityException(msg, sqle);
}
catch (NamingException ne)
{
String msg = "DataSourceResourceLoader: database problem while "
+ operation + " of '" + name + "': ";
log.error(msg, ne);
throw new VelocityException(msg, ne);
}
finally
{
closeResultSet(rs);
closeStatement(ps);
closeDbConnection(conn);
}
}
return timeStamp;
}
/**
* Gets connection to the datasource specified through the configuration
* parameters.
*
* @return connection
*/
private Connection openDbConnection() throws NamingException, SQLException
{
if (dataSource != null)
{
return dataSource.getConnection();
}
if (ctx == null)
{
ctx = new InitialContext();
}
dataSource = (DataSource) ctx.lookup(dataSourceName);
return dataSource.getConnection();
}
/**
* Closes connection to the datasource
*/
private void closeDbConnection(final Connection conn)
{
if (conn != null)
{
try
{
conn.close();
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
String msg = "DataSourceResourceLoader: problem when closing connection";
log.error(msg, e);
throw new VelocityException(msg, e);
}
}
}
/**
* Closes the result set.
*/
private void closeResultSet(final ResultSet rs)
{
if (rs != null)
{
try
{
rs.close();
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
String msg = "DataSourceResourceLoader: problem when closing result set";
log.error(msg, e);
throw new VelocityException(msg, e);
}
}
}
/**
* Closes the PreparedStatement.
*/
private void closeStatement(PreparedStatement ps)
{
if (ps != null)
{
try
{
ps.close();
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
String msg = "DataSourceResourceLoader: problem when closing PreparedStatement ";
log.error(msg, e);
throw new VelocityException(msg, e);
}
}
}
/**
* Creates the following PreparedStatement query :
*
* SELECT columnNames FROM tableName WHERE keyColumn
* = 'templateName'
*
* where keyColumn is a class member set in init()
*
* @param conn connection to datasource
* @param columnNames columns to fetch from datasource
* @param tableName table to fetch from
* @param keyColumn column whose value should match templateName
* @param templateName name of template to fetch
* @return PreparedStatement
*/
protected PreparedStatement getStatement(final Connection conn,
final String columnNames,
final String tableName,
final String keyColumn,
final String templateName) throws SQLException
{
PreparedStatement ps = conn.prepareStatement("SELECT " + columnNames + " FROM "+ tableName + " WHERE " + keyColumn + " = ?");
ps.setString(1, templateName);
return ps;
}
}