/* (c) 2014 - 2016 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.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.List; import net.opengis.wfs.FeatureCollectionType; import org.geoserver.catalog.ResourceInfo; import org.geoserver.ows.Dispatcher; import org.geoserver.platform.ServiceException; import org.geoserver.template.DirectTemplateFeatureCollectionFactory; import org.geoserver.template.FeatureWrapper; import org.geoserver.template.GeoServerTemplateLoader; import org.geoserver.wms.GetFeatureInfoRequest; import org.geoserver.wms.WMS; import org.geotools.feature.FeatureCollection; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.Name; import freemarker.template.Configuration; import freemarker.template.SimpleHash; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; /** * Produces a FeatureInfo response in HTML. Relies on {@link AbstractFeatureInfoResponse} and the * feature delegate to do most of the work, just implements an HTML based writeTo method. * * @author James Macgill, PSU * @author Andrea Aime, TOPP * @version $Id$ */ public class HTMLFeatureInfoOutputFormat extends GetFeatureInfoOutputFormat { private static final String FORMAT = "text/html"; private static Configuration templateConfig; private static DirectTemplateFeatureCollectionFactory tfcFactory = new DirectTemplateFeatureCollectionFactory(); static { // initialize the template engine, this is static to maintain a cache // over instantiations of kml writer templateConfig = new Configuration(); templateConfig.setObjectWrapper(new FeatureWrapper(tfcFactory) { @Override public TemplateModel wrap(Object object) throws TemplateModelException { if (object instanceof FeatureCollection) { SimpleHash map = (SimpleHash) super.wrap(object); map.put("request", Dispatcher.REQUEST.get().getKvp()); map.put("environment", new EnvironmentVariablesTemplateModel()); return map; } return super.wrap(object); } }); } GeoServerTemplateLoader templateLoader; private WMS wms; public HTMLFeatureInfoOutputFormat(final WMS wms) { super(FORMAT); this.wms = wms; } /** * Writes the image to the client. * * @param out * The output stream to write to. * * @throws org.vfny.geoserver.ServiceException * For problems with geoserver * @throws java.io.IOException * For problems writing the output. */ @SuppressWarnings("unchecked") @Override public void write(FeatureCollectionType results, GetFeatureInfoRequest request, OutputStream out) throws ServiceException, IOException { // setup the writer final Charset charSet = wms.getCharSet(); final OutputStreamWriter osw = new OutputStreamWriter(out, charSet); try { // if there is only one feature type loaded, we allow for header/footer customization, // otherwise we stick with the generic ones Template header = null; Template footer = null; List<FeatureCollection> collections = results.getFeature(); if (collections.size() == 1) { header = getTemplate(FeatureCollectionDecorator.getName(collections.get(0)), "header.ftl", charSet); footer = getTemplate(FeatureCollectionDecorator.getName(collections.get(0)), "footer.ftl", charSet); } else { // load the default ones header = getTemplate(null, "header.ftl", charSet); footer = getTemplate( null, "footer.ftl", charSet); } try { header.process(null, osw); } catch (TemplateException e) { String msg = "Error occured processing header template."; throw (IOException) new IOException(msg).initCause(e); } // process content template for all feature collections found for (int i=0; i < collections.size(); i++ ) { FeatureCollection fc = collections.get(i); if (fc != null && fc.size() > 0) { Template content = null; if (! (fc.getSchema() instanceof SimpleFeatureType)) { //if there is a specific template for complex features, use that. content = getTemplate(FeatureCollectionDecorator.getName(fc), "complex_content.ftl", charSet); } if (content==null) { content = getTemplate(FeatureCollectionDecorator.getName(fc), "content.ftl", charSet); } try { content.process(fc, osw); } catch (TemplateException e) { String msg = "Error occured processing content template " + content.getName() + " for " + request.getQueryLayers().get(i).getName(); throw (IOException) new IOException(msg).initCause(e); } } } // if a template footer was loaded (ie, there were only one feature // collection), process it if (footer != null) { try { footer.process(null, osw); } catch (TemplateException e) { String msg = "Error occured processing footer template."; throw (IOException) new IOException(msg).initCause(e); } } osw.flush(); } finally { //close any open iterators tfcFactory.purge(); } } /** * Uses a {@link GeoServerTemplateLoader TemplateLoader} too look up for the template file named * <code>templateFilename</code> for the given <code>featureType</code>. * * @param name * the name of the featureType to look the template for * In case you want to load the default template you can leave this argument null * @param templateFileName * the name of the template to look for * @param charset * the encoding to apply to the resulting {@link Template} * @return the template named <code>templateFileName</code> * @throws IOException * if the template can't be loaded */ Template getTemplate(Name name, String templateFileName, Charset charset) throws IOException { ResourceInfo ri = null; if (name != null) { ri = wms.getResourceInfo(name); // ri can be null if the type is the result of a rendering transformation } synchronized (templateConfig) { // setup template subsystem if (templateLoader == null) { templateLoader = new GeoServerTemplateLoader(getClass()); } templateLoader.setResource(ri); templateConfig.setTemplateLoader(templateLoader); Template t = templateConfig.getTemplate(templateFileName); t.setEncoding(charset.name()); return t; } } @Override public String getCharset(){ return wms.getGeoServer().getSettings().getCharset(); } }