/* Copyright (c) 2001 - 2010 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.catalog; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.xml.transform.TransformerException; import org.geoserver.ows.util.RequestUtils; import org.geotools.factory.CommonFactoryFinder; import org.geotools.sld.v1_1.SLDConfiguration; import org.geotools.styling.NamedLayer; import org.geotools.styling.NamedStyle; import org.geotools.styling.SLDParser; import org.geotools.styling.SLDTransformer; import org.geotools.styling.Style; import org.geotools.styling.StyleFactory; import org.geotools.styling.StyledLayerDescriptor; import org.geotools.styling.UserLayer; import org.geotools.util.Version; import org.geotools.util.logging.Logging; import org.geotools.xml.Parser; import org.vfny.geoserver.util.SLDValidator; import org.xml.sax.InputSource; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; /** * Provides methods to parse/encode style documents. * <p> * Currently SLD versions 1.0, and 1.1 are supported. * </p> * @author Justin Deoliveira, OpenGeo */ public class Styles { /** logger */ static Logger LOGGER = Logging.getLogger("org.geoserver.wms"); /** * number of bytes to "look ahead" when pre parsing xml document. * TODO: make this configurable, and possibley link it to the same value * used by the ows dispatcher. */ static int XML_LOOKAHEAD = 8192; static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null); /** * Parses a style document into a StyleLayerDescriptor determining style type/version * from the content itself. * * @param input a File, Reader, or InputStream object. * * @return The parsed style. * * @throws IOException Any parsing errors that occur. * @throws IllegalArgumentException If the type of the style can not be determined. */ public static StyledLayerDescriptor parse(Object input) throws IOException { Object[] obj = getVersionAndReader(input); return parse(obj[1], (Version)obj[0]); } /** * Parses a style document into a StyledLayerDescriptor object explicitly specifying version. * <p> * </p> * @param input a File, Reader, or InputStream object. * @param version The SLD version * * @return The parsed StyleLayerDescriptor. * * @throws IOException Any parsing errors that occur. * @throws IllegalArgumentException If the specified version is not supported. */ public static StyledLayerDescriptor parse(Object input, Version version) throws IOException { return Handler.lookup(version).parse(input); } /** * Encodes a StyledLayerDescriptor object to a style document. * <p> * </p> * @param sld The StyledLayerDescriptor object * @param version The SLD version * @param format Specifies if the serialized SLD should be formatted or not. * @param output The output stream to serialize to. * * @throws IOException Any encoding errors that occur. * @throws IllegalArgumentException If the specified version is not supported. */ public static void encode(StyledLayerDescriptor sld, Version version, boolean format, OutputStream output) throws IOException { Handler.lookup(version).encode(sld, format, output); } /** * Performs schema validation on an style document determining style type from the content * itself. * * @param input A File, Reader, or InputStream object. * * @return A list of validation exceptions, empty if no errors are present and the document is * valid. * * @throws IOException Any parsing errors that occur. * @throws IllegalArgumentException If the specified version is not supported. */ public static List<Exception> validate(Object input) throws IOException { Object[] obj = getVersionAndReader(input); return validate(obj[1], (Version)obj[0]); } /** * Performs schema validation on an style document, specifying the version. * * @param input A File, Reader, or InputStream object. * @param version The SLD version * * @return A list of validation exceptions, empty if no errors are present and the document is * valid. * * @throws IOException Any parsing errors that occur. * @throws IllegalArgumentException If the specified version is not supported. */ public static List<Exception> validate(Object input, Version version) throws IOException { return Handler.lookup(version).validate(input); } /** * Convenience method to pull a UserSyle from a StyledLayerDescriptor. * <p> * This method will return the first UserStyle it encounters in the StyledLayerDescriptor tree. * </p> * @param sld The StyledLayerDescriptor object. * * @return The UserStyle, or <code>null</code> if no such style could be found. */ public static Style style(StyledLayerDescriptor sld) { for (int i = 0; i < sld.getStyledLayers().length; i++) { Style[] styles = null; if (sld.getStyledLayers()[i] instanceof NamedLayer) { NamedLayer layer = (NamedLayer) sld.getStyledLayers()[i]; styles = layer.getStyles(); } else if(sld.getStyledLayers()[i] instanceof UserLayer) { UserLayer layer = (UserLayer) sld.getStyledLayers()[i]; styles = layer.getUserStyles(); } if (styles != null) { for (int j = 0; j < styles.length; i++) { if (!(styles[j] instanceof NamedStyle)) { return styles[j]; } } } } return null; } /** * Convenience method to wrap a UserStyle in a StyledLayerDescriptor object. * <p> * This method wraps the UserStyle in a NamedLayer, and wraps the result in a StyledLayerDescriptor. * </p> * @param style The UserStyle. * * @return The StyledLayerDescriptor. */ public static StyledLayerDescriptor sld(Style style) { StyledLayerDescriptor sld = styleFactory.createStyledLayerDescriptor(); NamedLayer layer = styleFactory.createNamedLayer(); layer.setName(style.getName()); sld.addStyledLayer(layer); layer.addStyle(style); return sld; } public static Version findVersion(Object input) throws IOException{ Object[] versionAndReader = getVersionAndReader(input); return (Version) versionAndReader[0]; } /** * Helper method for finding which style handler/version to use from the actual content. */ static Object[] getVersionAndReader(Object input) throws IOException { //need to determine version of sld from actual content BufferedReader reader = null; if (input instanceof InputStream) { reader = RequestUtils.getBufferedXMLReader((InputStream) input, 8192); } else { reader = RequestUtils.getBufferedXMLReader(toReader(input), 8192); } if (!reader.ready()) { return null; } String version; try { //create stream parser XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(false); //parse root element XmlPullParser parser = factory.newPullParser(); parser.setInput(reader); parser.nextTag(); version = null; for (int i = 0; i < parser.getAttributeCount(); i++) { if ("version".equals(parser.getAttributeName(i))) { version = parser.getAttributeValue(i); } } parser.setInput(null); } catch (XmlPullParserException e) { throw (IOException) new IOException("Error parsing content").initCause(e); } //reset input stream reader.reset(); if (version == null) { LOGGER.warning("Could not determine SLD version from content. Assuming 1.0.0"); version = "1.0.0"; } return new Object[]{new Version(version), reader}; } static Reader toReader(Object input) throws IOException { if (input instanceof Reader) { return (Reader) input; } if (input instanceof InputStream) { return new InputStreamReader((InputStream)input); } if (input instanceof File) { return new FileReader((File)input); } throw new IllegalArgumentException("Unable to turn " + input + " into reader"); } public static enum Handler { SLD_10("1.0.0") { @Override public StyledLayerDescriptor parse(Object input) throws IOException { SLDParser p = parser(input); StyledLayerDescriptor sld = p.parseSLD(); if (sld.getStyledLayers().length == 0) { //most likely a style that is not a valid sld, try to actually parse out a // style and then wrap it in an sld Style[] style = p.readDOM(); if (style.length > 0) { NamedLayer l = styleFactory.createNamedLayer(); l.addStyle(style[0]); sld.addStyledLayer(l); } } return sld; } @Override protected List<Exception> validate(Object input) throws IOException { return new SLDValidator().validateSLD(new InputSource(toReader(input))); } @Override public void encode(StyledLayerDescriptor sld, boolean format, OutputStream output) throws IOException { SLDTransformer tx = new SLDTransformer(); if (format) { tx.setIndentation(2); } try { tx.transform( sld, output ); } catch (TransformerException e) { throw (IOException) new IOException("Error writing style").initCause(e); } } SLDParser parser(Object input) throws IOException { if (input instanceof File) { return new SLDParser(styleFactory, (File) input); } else { return new SLDParser(styleFactory, toReader(input)); } } }, SLD_11("1.1.0") { @Override public StyledLayerDescriptor parse(Object input) throws IOException { SLDConfiguration sld = new SLDConfiguration(); try { return (StyledLayerDescriptor) new Parser(sld).parse(toReader(input)); } catch(Exception e) { if (e instanceof IOException) throw (IOException) e; throw (IOException) new IOException().initCause(e); } } @Override protected List<Exception> validate(Object input) throws IOException { SLDConfiguration sld = new SLDConfiguration(); Parser p = new Parser(sld); p.setValidating(true); try { p.parse(toReader(input)); return p.getValidationErrors(); } catch(Exception e) { e.printStackTrace(); List validationErrors = new ArrayList<Exception>(p.getValidationErrors()); validationErrors.add(0, e); return validationErrors; } } @Override public void encode(StyledLayerDescriptor sld, boolean format, OutputStream output) throws IOException { // TODO Auto-generated method stub } }; private Version version; private Handler(String version) { this.version = new org.geotools.util.Version(version); } public Version getVersion() { return version; } protected abstract StyledLayerDescriptor parse(Object input) throws IOException; protected abstract void encode(StyledLayerDescriptor sld, boolean format, OutputStream output) throws IOException; protected abstract List<Exception> validate(Object input) throws IOException; public static Handler lookup(Version version) { for (Handler h : values()) { if (h.getVersion().equals(version)) { return h; } } throw new IllegalArgumentException("No support for SLD " + version); } }; }