![JAR search and dependency download from the Maven repository](/logo.png)
bboss.org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader Maven / Gradle / Ivy
Show all versions of bboss-velocity Show documentation
package bboss.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 bboss.org.apache.velocity.exception.ResourceNotFoundException;
import bboss.org.apache.velocity.exception.VelocityException;
import bboss.org.apache.velocity.runtime.resource.Resource;
import bboss.org.apache.velocity.util.ClassUtils;
import bboss.org.apache.velocity.util.ExtProperties;
import org.apache.commons.lang3.StringUtils;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
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.loaders = file, ds
*
* resource.loader.ds.description = Velocity DataSource Resource Loader
* resource.loader.ds.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader
* resource.loader.ds.resource.datasource_url = java:comp/env/jdbc/Velocity
* resource.loader.ds.resource.table = tb_velocity_template
* resource.loader.ds.resource.key_column = id_template
* resource.loader.ds.resource.template_column = template_definition
* resource.loader.ds.resource.timestamp_column = template_timestamp
* resource.loader.ds.cache = false
* resource.loader.ds.modification_check_interval = 60
* resource.loader.ds.statements_pool_max_size = 50
*
* 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("resource.loader.ds.instance",ds);
*
* The property resource.loader.ds.class
should be left out, otherwise all the other
* properties in velocity.properties would remain the same.
*
* Example WEB-INF/web.xml:
*
*
* <resource-ref>
* <description>Velocity template DataSource</description>
* <res-ref-name>jdbc/Velocity</res-ref-name>
* <res-type>javax.sql.DataSource</res-type>
* <res-auth>Container</res-auth>
* </resource-ref>
*
*
* and Tomcat 4 server.xml file:
*
* [...]
* <Context path="/exampleVelocity" docBase="exampleVelocity" debug="0">
* [...]
* <ResourceParams name="jdbc/Velocity">
* <parameter>
* <name>driverClassName</name>
* <value>org.hsql.jdbcDriver</value>
* </parameter>
* <parameter>
* <name>driverName</name>
* <value>jdbc:HypersonicSQL:database</value>
* </parameter>
* <parameter>
* <name>user</name>
* <value>database_username</value>
* </parameter>
* <parameter>
* <name>password</name>
* <value>database_password</value>
* </parameter>
* </ResourceParams>
* [...]
* </Context>
* [...]
*
*
* 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
* );
*
* Prior to Velocity 2.4, this class should not be considered thread-safe.
* Since Velocity 2.4, the handling of JDBC connections and prepared statements is delegated to the
* {@link bboss.org.apache.velocity.runtime.resource.loader.DatabaseObjectsFactory} instance. The default class for this
* database objects factory is {@link bboss.org.apache.velocity.runtime.resource.loader.DefaultDatabaseObjectsFactory},
* which obtains a new connection from the data source and prepares statements at each query. You can configure this
* resource loader to use the {@link bboss.org.apache.velocity.runtime.resource.loader.CachingDatabaseObjectsFactory} which
* will keep a single connection and tries to reuse prepared statements.
* statements
*
* @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 static final String DATABASE_OBJECTS_FACTORY_DEFAULT_CLASS = "bboss.org.apache.velocity.runtime.resource.loader.DefaultDatabaseObjectsFactory";
private DataSource dataSource;
private DatabaseObjectsFactory factory;
private String templateColumn;
private String timestampColumn;
private String templateSQL;
private String timestampSQL;
private class SelfCleaningReader extends FilterReader
{
private final ResultSet resultSet;
public SelfCleaningReader(Reader reader, ResultSet resultSet)
{
super(reader);
this.resultSet = resultSet;
}
@Override
public void close() throws IOException
{
super.close();
try
{
resultSet.close();
factory.releaseStatement(templateSQL, (PreparedStatement)resultSet.getStatement());
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
// ignore
}
}
}
/**
* @see ResourceLoader#init(bboss.org.apache.velocity.util.ExtProperties)
*/
@Override
public void init(ExtProperties configuration)
{
String tableName = StringUtils.trim(configuration.getString("resource.table"));
String keyColumn = StringUtils.trim(configuration.getString("resource.key_column"));
templateColumn = StringUtils.trim(configuration.getString("resource.template_column"));
timestampColumn = StringUtils.trim(configuration.getString("resource.timestamp_column"));
templateSQL = "SELECT " + templateColumn + " FROM " + tableName + " WHERE " + keyColumn + " = ?";
timestampSQL = "SELECT " + timestampColumn + " FROM " + tableName + " WHERE " + keyColumn + " = ?";
String dataSourceName = StringUtils.trim(configuration.getString("resource.datasource_url"));
if (dataSource != null)
{
log.debug("DataSourceResourceLoader: using dataSource instance with table \"{}\"", tableName);
log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn);
}
else if (dataSourceName != null)
{
log.debug("DataSourceResourceLoader: using \"{}\" datasource with table \"{}\"", dataSourceName, tableName);
log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn);
try
{
dataSource = (DataSource) new InitialContext().lookup(dataSourceName);
}
catch (NamingException ne)
{
throw new VelocityException("could not lookup datasource for name: " + dataSourceName, ne);
}
log.trace("DataSourceResourceLoader initialized.");
}
else
{
String msg = "DataSourceResourceLoader not properly initialized. No DataSource was identified.";
log.error(msg);
throw new RuntimeException(msg);
}
String factoryClassName = configuration.getString("database_objects_factory.class");
if (factoryClassName == null)
{
factoryClassName = DATABASE_OBJECTS_FACTORY_DEFAULT_CLASS;
}
try
{
Class> factoryClass = ClassUtils.getClass(factoryClassName);
factory = (DatabaseObjectsFactory) factoryClass.getDeclaredConstructor().newInstance();
factory.init(dataSource, configuration.subset("database_objects_factory"));
}
catch (Exception e)
{
throw new VelocityException("could not find database objects factory class", e);
}
log.trace("DataSourceResourceLoader initialized.");
}
/**
* 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)
{
if (factory != null)
{
throw new VelocityException("cannot change data source after initialization");
}
this.dataSource = dataSource;
}
/**
* @see ResourceLoader#isSourceModified(bboss.org.apache.velocity.runtime.resource.Resource)
*/
@Override
public boolean isSourceModified(final Resource resource)
{
return (resource.getLastModified() !=
readLastModified(resource, "checking timestamp"));
}
/**
* @see ResourceLoader#getLastModified(bboss.org.apache.velocity.runtime.resource.Resource)
*/
@Override
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
*/
@Override
public synchronized Reader getResourceReader(final String name, String encoding)
throws ResourceNotFoundException
{
if (StringUtils.isEmpty(name))
{
throw new ResourceNotFoundException("DataSourceResourceLoader: Template name was empty or null");
}
ResultSet rs = null;
try
{
PreparedStatement statement = factory.prepareStatement(templateSQL);
rs = fetchResult(statement, name);
if (rs.next())
{
Reader reader = getReader(rs, templateColumn, encoding);
if (reader == null)
{
throw new ResourceNotFoundException("DataSourceResourceLoader: "
+ "template column for '"
+ name + "' is null");
}
return new SelfCleaningReader(reader, rs);
}
else
{
throw new ResourceNotFoundException("DataSourceResourceLoader: "
+ "could not find resource '"
+ name + "'");
}
}
catch (Exception e)
{
String msg = "DataSourceResourceLoader: database problem while getting resource '"
+ name + "': ";
log.error(msg, e);
throw new ResourceNotFoundException(msg);
}
}
/**
* 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
{
PreparedStatement statement = null;
ResultSet rs = null;
try
{
statement = factory.prepareStatement(timestampSQL);
rs = fetchResult(statement, name);
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 (Exception e)
{
String msg = "DataSourceResourceLoader: database problem while "
+ operation + " of '" + name + "': ";
log.error(msg, e);
throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
}
finally
{
closeResultSet(rs);
if (statement != null)
{
try
{
factory.releaseStatement(timestampSQL, statement);
}
catch (SQLException sqle)
{
// just log, don't throw
log.debug("DataSourceResourceLoader: error releasing prepared statement", sqle);
}
}
}
}
return timeStamp;
}
/**
* Closes the result set.
*/
private void closeResultSet(final ResultSet rs)
{
if (rs != null)
{
try
{
rs.close();
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception e)
{
// ignore
}
}
}
/**
* Fetches the result for a given template name.
* Inherit this method if there is any calculation to perform on the template name.
*
* @param ps target prepared statement
* @param templateName input template name
* @return result set
* @throws SQLException
*/
protected ResultSet fetchResult(
final PreparedStatement ps,
final String templateName
) throws SQLException
{
ps.setString(1, templateName);
return ps.executeQuery();
}
/**
* Gets a reader from a result set's column.
* @param resultSet result set
* @param column template column
* @param encoding template encoding (unused)
* @return reader
* @throws SQLException
* @deprecated the 'encoding' parameter is useless, it should have been set in the database. Use {@link #getReader(ResultSet, String)}
*/
protected Reader getReader(ResultSet resultSet, String column, String encoding)
throws SQLException
{
return getReader(resultSet, column);
}
/**
* Gets a reader from a result set's column.
* @param resultSet result set
* @param column template column
* @return reader
* @throws SQLException
*/
protected Reader getReader(ResultSet resultSet, String column)
throws SQLException
{
return resultSet.getCharacterStream(column);
}
/**
* Frees all resources.
*/
public void clear()
{
if (factory != null)
{
factory.clear();
}
}
}