/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.featureinfo;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.geoserver.template.FeatureWrapper;
import org.geoserver.template.GeoServerTemplateLoader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* Executes a template for a feature.
* <p>
* Usage:
* <pre>
* <code>
* Feature feature = ... //some feature
* Writer writer = ... //some writer
*
* FeatureTemplate template = new FeatureTemplate();
*
* //title
* template.title( feature );
*
* //description
* template.description( feature );
* </code>
* </pre>
* </p>
* For performance reasons the template lookups will be cached, so it's advised to
* use the same FeatureTemplate object in a loop that encodes various features, but not
* to cache it for a long time (static reference).
* Moreover, FeatureTemplate is not thread safe, so instantiate one for each thread.
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
* @author Andrea Aime, TOPP
*
*/
public class FeatureTemplate {
/**
* The template configuration used for placemark descriptions
*/
static Configuration templateConfig;
static {
//initialize the template engine, this is static to maintain a cache
// over instantiations of kml writer
templateConfig = new Configuration();
templateConfig.setObjectWrapper(new FeatureWrapper());
//set the default output formats for dates
templateConfig.setDateFormat("MM/dd/yyyy");
templateConfig.setDateTimeFormat("MM/dd/yyyy HH:mm:ss");
templateConfig.setTimeFormat("HH:mm:ss");
//set the default locale to be US and the
//TODO: this may be somethign we want to configure/change
templateConfig.setLocale(Locale.US);
templateConfig.setNumberFormat("0.###########");
}
/**
* The pattern used by DATETIME_FORMAT
*/
public static String DATE_FORMAT_PATTERN = "MM/dd/yy";
/**
* Default date format produced by templates
*/
public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_PATTERN);
/**
* The pattern used by DATETIME_FORMAT
*/
public static String DATETIME_FORMAT_PATTERN = "MM/dd/yy HH:mm:ss";
/**
* Default datetime format produced by templates
*/
public static SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_PATTERN);
/**
* The pattern used by DATETIME_FORMAT
*/
public static String TIME_FORMAT_PATTERN = "HH:mm:ss";
/**
* Default time format produced by templates
*/
public static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat();
/**
* Template cache used to avoid paying the cost of template lookup for each feature
*/
Map templateCache = new HashMap();
/**
* Cached writer used for plain conversion from Feature to String. Improves performance
* significantly compared to an OutputStreamWriter over a ByteOutputStream.
*/
CharArrayWriter caw = new CharArrayWriter();
/**
* Executes the title template for a feature writing the results to an
* output stream.
* <p>
* This method is convenience for:
* <code>
* description( feature, new OutputStreamWriter( output ) );
* </code>
* </p>
*
* @param feature The feature to execute the template against.
* @param output The output to write the result of the template to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void title(SimpleFeature feature, OutputStream output)
throws IOException {
title(feature, new OutputStreamWriter(output, Charset.forName("UTF-8")));
}
/**
* Executes the link template for a feature writing the results to an
* output stream.
* <p>
* This method is convenience for:
* <code>
* link( feature, new OutputStreamWriter( output ) );
* </code>
* </p>
*
* @param feature The feature to execute the template against.
* @param output The output to write the result of the template to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void link(SimpleFeature feature, OutputStream output)
throws IOException {
link(feature, new OutputStreamWriter(output, Charset.forName("UTF-8")));
}
/**
* Executes the description template for a feature writing the results to an
* output stream.
* <p>
* This method is convenience for:
* <code>
* description( feature, new OutputStreamWriter( output ) );
* </code>
* </p>
*
* @param feature The feature to execute the template against.
* @param output The output to write the result of the template to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void description(SimpleFeature feature, OutputStream output)
throws IOException {
description(feature, new OutputStreamWriter(output, Charset.forName("UTF-8")));
}
/**
* Executes the title template for a feature writing the results to a
* writer.
*
* @param feature The feature to execute the template against.
* @param writer The writer to write the template output to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void title(SimpleFeature feature, Writer writer) throws IOException {
execute(feature, feature.getFeatureType(), writer, "title.ftl",null);
}
/**
* Executes the link template for a feature writing the results to a
* writer.
*
* @param feature The feature to execute the template against.
* @param writer The writer to write the template output to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void link(SimpleFeature feature, Writer writer) throws IOException {
execute(feature, feature.getFeatureType(), writer, "link.ftl",null);
}
/**
* Executes the description template for a feature writing the results to a
* writer.
*
* @param feature The feature to execute the template against.
* @param writer The writer to write the template output to.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public void description(SimpleFeature feature, Writer writer)
throws IOException {
execute(feature, feature.getFeatureType(), writer, "description.ftl",null);
}
/**
* Executes the title template for a feature returning the result as a
* string.
*
* @param feature The feature to execute the template against.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public String title(SimpleFeature feature) throws IOException {
caw.reset();
title(feature, caw);
return caw.toString();
}
/**
* Executes the link template for a feature returning the result as a
* string.
*
* @param feature The feature to execute the template against.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public String link(SimpleFeature feature) throws IOException {
caw.reset();
link(feature, caw);
return caw.toString();
}
/**
* Executes the description template for a feature returning the result as a
* string.
*
* @param feature The feature to execute the template against.
*
* @throws IOException Any errors that occur during execution of the template.
*/
public String description(SimpleFeature feature) throws IOException {
caw.reset();
description(feature, caw);
return caw.toString();
}
/**
* Executes a template for the feature writing the results to a writer.
* <p>
* The template to execute is secified via the <tt>template</tt>, and
* <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
* class from which <tt>template</tt> shoould be loaded relative to in teh
* case where the user has not specified an override in the data directory.
* </p>
* @param feature The feature to execute the template against.
* @param writer The writer for output.
* @param template The template name.
* @param lookup The class to lookup the template relative to.
*
*/
public void template(SimpleFeature feature, Writer writer, String template, Class lookup)
throws IOException {
execute(feature,feature.getFeatureType(),writer,template,lookup);
}
/**
* Executes a template for the feature writing the results to an output stream.
* <p>
* The template to execute is secified via the <tt>template</tt>, and
* <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
* class from which <tt>template</tt> shoould be loaded relative to in teh
* case where the user has not specified an override in the data directory.
* </p>
* @param feature The feature to execute the template against.
* @param output The output.
* @param template The template name.
* @param lookup The class to lookup the template relative to.
*
*/
public void template(SimpleFeature feature, OutputStream output, String template, Class lookup)
throws IOException {
template( feature, new OutputStreamWriter( output ), template, lookup );
}
/**
* Executes a template for the feature returning the result as a string.
* <p>
* The template to execute is secified via the <tt>template</tt>, and
* <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
* class from which <tt>template</tt> shoould be loaded relative to in teh
* case where the user has not specified an override in the data directory.
* </p>
* @param feature The feature to execute the template against.
* @param template The template name.
* @param lookup The class to lookup the template relative to.
*
*/
public String template(SimpleFeature feature, String template, Class lookup) throws IOException {
caw.reset();
template(feature,caw,template,lookup);
return caw.toString();
}
/*
* Internal helper method to exceute the template against feature or
* feature collection.
*/
private void execute(Object feature, SimpleFeatureType featureType, Writer writer, String template,Class lookup)
throws IOException {
Template t = null;
t = lookupTemplate(featureType, template,lookup);
try {
t.process(feature, writer);
} catch (TemplateException e) {
String msg = "Error occured processing template.";
throw (IOException) new IOException(msg).initCause(e);
}
}
/**
* Returns the template for the specified feature type. Looking up templates is pretty
* expensive, so we cache templates by feture type and template.
*
*/
private Template lookupTemplate(SimpleFeatureType featureType, String template, Class lookup) throws IOException {
Template t;
// lookup the cache first
TemplateKey key = new TemplateKey(featureType, template);
t = (Template) templateCache.get(key);
if(t != null)
return t;
// otherwise, build a loader and do the lookup
GeoServerTemplateLoader templateLoader =
new GeoServerTemplateLoader(lookup!=null?lookup:getClass());
templateLoader.setFeatureType(featureType);
//Configuration is not thread safe
synchronized (templateConfig) {
templateConfig.setTemplateLoader(templateLoader);
t = templateConfig.getTemplate(template);
t.setEncoding("UTF-8");
}
templateCache.put(key, t);
return t;
}
/**
* Returns true if the required template is empty or has its default content
*
* @param featureType
* @param template
* @param lookup
*
* @throws IOException
*/
public boolean isTemplateEmpty(SimpleFeatureType featureType, String template,
Class<FeatureTemplate> lookup, String defaultContent) throws IOException {
Template t = lookupTemplate(featureType, template, lookup);
if(t == null) {
return true;
}
// check if the template is empty
StringWriter sw = new StringWriter();
t.dump(sw);
// an empty template canonical form is "0\n".. weird!
String templateText = sw.toString();
return "".equals(templateText) || (defaultContent != null && defaultContent.equals(templateText));
}
private static class TemplateKey {
SimpleFeatureType type;
String template;
public TemplateKey(SimpleFeatureType type, String template) {
super();
this.type = type;
this.template = template;
}
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((template == null) ? 0 : template.hashCode());
result = PRIME * result + ((type == null) ? 0 : type.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final TemplateKey other = (TemplateKey) obj;
if (template == null) {
if (other.template != null)
return false;
} else if (!template.equals(other.template))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
}
}