001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.wms.responses.featureInfo;
006:
007: import freemarker.template.Configuration;
008: import freemarker.template.Template;
009: import freemarker.template.TemplateException;
010: import org.geoserver.template.FeatureWrapper;
011: import org.geoserver.template.GeoServerTemplateLoader;
012: import org.geotools.feature.Feature;
013: import org.geotools.feature.FeatureType;
014: import java.io.CharArrayWriter;
015: import java.io.IOException;
016: import java.io.OutputStream;
017: import java.io.OutputStreamWriter;
018: import java.io.Writer;
019: import java.nio.charset.Charset;
020: import java.text.SimpleDateFormat;
021: import java.util.HashMap;
022: import java.util.Locale;
023: import java.util.Map;
024:
025: /**
026: * Executes a template for a feature.
027: * <p>
028: * Usage:
029: * <pre>
030: * <code>
031: * Feature feature = ... //some feature
032: * Writer writer = ... //some writer
033: *
034: * FeatureTemplate template = new FeatureTemplate();
035: *
036: * //title
037: * template.title( feature );
038: *
039: * //description
040: * template.description( feature );
041: * </code>
042: * </pre>
043: * </p>
044: * For performance reasons the template lookups will be cached, so it's advised to
045: * use the same FeatureTemplate object in a loop that encodes various features, but not
046: * to cache it for a long time (static reference).
047: * Moreover, FeatureTemplate is not thread safe, so instantiate one for each thread.
048: * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
049: * @author Andrea Aime, TOPP
050: *
051: */
052: public class FeatureTemplate {
053: /**
054: * The template configuration used for placemark descriptions
055: */
056: static Configuration templateConfig;
057:
058: static {
059: //initialize the template engine, this is static to maintain a cache
060: // over instantiations of kml writer
061: templateConfig = new Configuration();
062: templateConfig.setObjectWrapper(new FeatureWrapper());
063:
064: //set the default output formats for dates
065: templateConfig.setDateFormat("MM/dd/yyyy");
066: templateConfig.setDateTimeFormat("MM/dd/yyyy HH:mm:ss");
067: templateConfig.setTimeFormat("HH:mm:ss");
068:
069: //set the default locale to be US and the
070: //TODO: this may be somethign we want to configure/change
071: templateConfig.setLocale(Locale.US);
072: templateConfig.setNumberFormat("0.###########");
073: }
074:
075: /**
076: * Default date format produced by templates
077: */
078: public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
079: "MM/dd/yy");
080: /**
081: * Default datetime format produced by templates
082: */
083: public static SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat(
084: "MM/dd/yy HH:mm:ss");
085: /**
086: * Default time format produced by templates
087: */
088: public static SimpleDateFormat TIME_FORMAT = new SimpleDateFormat(
089: "HH:mm:ss");
090:
091: /**
092: * Template cache used to avoid paying the cost of template lookup for each feature
093: */
094: Map templateCache = new HashMap();
095:
096: /**
097: * Cached writer used for plain conversion from Feature to String. Improves performance
098: * significantly compared to an OutputStreamWriter over a ByteOutputStream.
099: */
100: CharArrayWriter caw = new CharArrayWriter();
101:
102: /**
103: * Executes the title template for a feature writing the results to an
104: * output stream.
105: * <p>
106: * This method is convenience for:
107: * <code>
108: * description( feature, new OutputStreamWriter( output ) );
109: * </code>
110: * </p>
111: *
112: * @param feature The feature to execute the template against.
113: * @param output The output to write the result of the template to.
114: *
115: * @throws IOException Any errors that occur during execution of the template.
116: */
117: public void title(Feature feature, OutputStream output)
118: throws IOException {
119: title(feature, new OutputStreamWriter(output, Charset
120: .forName("UTF-8")));
121: }
122:
123: /**
124: * Executes the description template for a feature writing the results to an
125: * output stream.
126: * <p>
127: * This method is convenience for:
128: * <code>
129: * description( feature, new OutputStreamWriter( output ) );
130: * </code>
131: * </p>
132: *
133: * @param feature The feature to execute the template against.
134: * @param output The output to write the result of the template to.
135: *
136: * @throws IOException Any errors that occur during execution of the template.
137: */
138: public void description(Feature feature, OutputStream output)
139: throws IOException {
140: description(feature, new OutputStreamWriter(output, Charset
141: .forName("UTF-8")));
142: }
143:
144: /**
145: * Executes the title template for a feature writing the results to a
146: * writer.
147: *
148: * @param feature The feature to execute the template against.
149: * @param writer The writer to write the template output to.
150: *
151: * @throws IOException Any errors that occur during execution of the template.
152: */
153: public void title(Feature feature, Writer writer)
154: throws IOException {
155: execute(feature, feature.getFeatureType(), writer, "title.ftl",
156: null);
157: }
158:
159: /**
160: * Executes the description template for a feature writing the results to a
161: * writer.
162: *
163: * @param feature The feature to execute the template against.
164: * @param writer The writer to write the template output to.
165: *
166: * @throws IOException Any errors that occur during execution of the template.
167: */
168: public void description(Feature feature, Writer writer)
169: throws IOException {
170: execute(feature, feature.getFeatureType(), writer,
171: "description.ftl", null);
172: }
173:
174: /**
175: * Executes the title template for a feature returning the result as a
176: * string.
177: *
178: * @param feature The feature to execute the template against.
179: *
180: * @throws IOException Any errors that occur during execution of the template.
181: */
182: public String title(Feature feature) throws IOException {
183: caw.reset();
184: title(feature, caw);
185:
186: return caw.toString();
187: }
188:
189: /**
190: * Executes the description template for a feature returning the result as a
191: * string.
192: *
193: * @param feature The feature to execute the template against.
194: *
195: * @throws IOException Any errors that occur during execution of the template.
196: */
197: public String description(Feature feature) throws IOException {
198: caw.reset();
199: description(feature, caw);
200:
201: return caw.toString();
202: }
203:
204: /**
205: * Executes a template for the feature writing the results to a writer.
206: * <p>
207: * The template to execute is secified via the <tt>template</tt>, and
208: * <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
209: * class from which <tt>template</tt> shoould be loaded relative to in teh
210: * case where the user has not specified an override in the data directory.
211: * </p>
212: * @param feature The feature to execute the template against.
213: * @param writer The writer for output.
214: * @param template The template name.
215: * @param lookup The class to lookup the template relative to.
216: *
217: */
218: public void template(Feature feature, Writer writer,
219: String template, Class lookup) throws IOException {
220: execute(feature, feature.getFeatureType(), writer, template,
221: lookup);
222: }
223:
224: /**
225: * Executes a template for the feature writing the results to an output stream.
226: * <p>
227: * The template to execute is secified via the <tt>template</tt>, and
228: * <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
229: * class from which <tt>template</tt> shoould be loaded relative to in teh
230: * case where the user has not specified an override in the data directory.
231: * </p>
232: * @param feature The feature to execute the template against.
233: * @param output The output.
234: * @param template The template name.
235: * @param lookup The class to lookup the template relative to.
236: *
237: */
238: public void template(Feature feature, OutputStream output,
239: String template, Class lookup) throws IOException {
240: template(feature, new OutputStreamWriter(output), template,
241: lookup);
242: }
243:
244: /**
245: * Executes a template for the feature returning the result as a string.
246: * <p>
247: * The template to execute is secified via the <tt>template</tt>, and
248: * <tt>lookup</tt> parameters. The <tt>lookup</tt> is used to specify the
249: * class from which <tt>template</tt> shoould be loaded relative to in teh
250: * case where the user has not specified an override in the data directory.
251: * </p>
252: * @param feature The feature to execute the template against.
253: * @param template The template name.
254: * @param lookup The class to lookup the template relative to.
255: *
256: */
257: public String template(Feature feature, String template,
258: Class lookup) throws IOException {
259: caw.reset();
260: template(feature, caw, template, lookup);
261: return caw.toString();
262: }
263:
264: /*
265: * Internal helper method to exceute the template against feature or
266: * feature collection.
267: */
268: private void execute(Object feature, FeatureType featureType,
269: Writer writer, String template, Class lookup)
270: throws IOException {
271: Template t = null;
272:
273: t = lookupTemplate(featureType, template, lookup);
274:
275: try {
276: t.process(feature, writer);
277: } catch (TemplateException e) {
278: String msg = "Error occured processing template.";
279: throw (IOException) new IOException(msg).initCause(e);
280: }
281: }
282:
283: /**
284: * Returns the template for the specified feature type. Looking up templates is pretty
285: * expensive, so we cache templates by feture type and template.
286: *
287: */
288: private Template lookupTemplate(FeatureType featureType,
289: String template, Class lookup) throws IOException {
290: Template t;
291:
292: // lookup the cache first
293: TemplateKey key = new TemplateKey(featureType, template);
294: t = (Template) templateCache.get(key);
295: if (t != null)
296: return t;
297:
298: // otherwise, build a loader and do the lookup
299: GeoServerTemplateLoader templateLoader = new GeoServerTemplateLoader(
300: lookup != null ? lookup : getClass());
301: templateLoader.setFeatureType(featureType);
302:
303: //Configuration is not thread safe
304: synchronized (templateConfig) {
305: templateConfig.setTemplateLoader(templateLoader);
306: t = templateConfig.getTemplate(template);
307: t.setEncoding("UTF-8");
308: }
309: templateCache.put(key, t);
310: return t;
311: }
312:
313: private static class TemplateKey {
314: FeatureType type;
315: String template;
316:
317: public TemplateKey(FeatureType type, String template) {
318: super ();
319: this .type = type;
320: this .template = template;
321: }
322:
323: public int hashCode() {
324: final int PRIME = 31;
325: int result = 1;
326: result = PRIME * result
327: + ((template == null) ? 0 : template.hashCode());
328: result = PRIME * result
329: + ((type == null) ? 0 : type.hashCode());
330: return result;
331: }
332:
333: public boolean equals(Object obj) {
334: if (this == obj)
335: return true;
336: if (obj == null)
337: return false;
338: if (getClass() != obj.getClass())
339: return false;
340: final TemplateKey other = (TemplateKey) obj;
341: if (template == null) {
342: if (other.template != null)
343: return false;
344: } else if (!template.equals(other.template))
345: return false;
346: if (type == null) {
347: if (other.type != null)
348: return false;
349: } else if (!type.equals(other.type))
350: return false;
351: return true;
352: }
353: }
354: }
|