/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.catalog;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.transform.TransformerException;
import org.apache.commons.io.IOUtils;
import org.geoserver.ows.util.RequestUtils;
import org.geotools.data.DataUtilities;
import org.geotools.sld.v1_1.SLD;
import org.geotools.sld.v1_1.SLDConfiguration;
import org.geotools.styling.DefaultResourceLocator;
import org.geotools.styling.NamedLayer;
import org.geotools.styling.ResourceLocator;
import org.geotools.styling.SLDParser;
import org.geotools.styling.SLDTransformer;
import org.geotools.styling.Style;
import org.geotools.styling.StyledLayerDescriptor;
import org.geotools.util.Version;
import org.geotools.util.logging.Logging;
import org.geotools.xml.Encoder;
import org.geotools.xml.Parser;
import org.vfny.geoserver.util.SLDValidator;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* SLD style handler.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class SLDHandler extends StyleHandler {
static Logger LOGGER = Logging.getLogger(SLDHandler.class);
/**
* 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;
public static final String FORMAT = "sld";
public static final Version VERSION_10 = new Version("1.0.0");
public static final Version VERSION_11 = new Version("1.1.0");
public static final String MIMETYPE_10 = "application/vnd.ogc.sld+xml";
public static final String MIMETYPE_11 = "application/vnd.ogc.se+xml";
static final Map<StyleType, String> TEMPLATES = new HashMap<StyleType, String>();
static {
try {
TEMPLATES.put(StyleType.POINT, IOUtils.toString(SLDHandler.class
.getResourceAsStream("template_point.sld")));
TEMPLATES.put(StyleType.POLYGON, IOUtils.toString(SLDHandler.class
.getResourceAsStream("template_polygon.sld")));
TEMPLATES.put(StyleType.LINE, IOUtils.toString(SLDHandler.class
.getResourceAsStream("template_line.sld")));
TEMPLATES.put(StyleType.RASTER, IOUtils.toString(SLDHandler.class
.getResourceAsStream("template_raster.sld")));
TEMPLATES.put(StyleType.GENERIC, IOUtils.toString(SLDHandler.class
.getResourceAsStream("template_generic.sld")));
} catch (IOException e) {
throw new RuntimeException("Error loading up the style templates", e);
}
}
public SLDHandler() {
super("SLD", FORMAT);
}
@Override
public List<Version> getVersions() {
return Arrays.asList(VERSION_10, VERSION_11);
}
@Override
public String getCodeMirrorEditMode() {
return "xml";
}
@Override
public String getStyle(StyleType type, Color color, String colorName, String layerName) {
String template = TEMPLATES.get(type);
String colorCode = Integer.toHexString(color.getRGB());
colorCode = colorCode.substring(2, colorCode.length());
return template.replace("${colorName}", colorName).replace(
"${colorCode}", "#" + colorCode).replace("${layerName}", layerName);
}
@Override
public String mimeType(Version version) {
if (version != null && VERSION_11.equals(version)) {
return MIMETYPE_11;
}
return MIMETYPE_10;
}
@Override
public Version versionForMimeType(String mimeType) {
if (mimeType.equals(MIMETYPE_11)) {
return VERSION_11;
}
return VERSION_10;
}
@Override
public StyledLayerDescriptor parse(Object input, Version version, ResourceLocator resourceLocator, EntityResolver entityResolver) throws IOException {
if (version == null) {
Object[] versionAndReader = getVersionAndReader(input);
version = (Version) versionAndReader[0];
input = versionAndReader[1];
}
if (VERSION_11.compareTo(version) == 0) {
return parse11(input, resourceLocator, entityResolver);
}
else {
return parse10(input, resourceLocator, entityResolver);
}
}
StyledLayerDescriptor parse10(Object input, ResourceLocator resourceLocator, EntityResolver entityResolver)
throws IOException {
Reader reader = null;
try {
// we need to close the reader if we grab one, but if it's a file it has
// to stay as such to allow relative resource resolution during the parse
if(!(input instanceof File)) {
reader = toReader(input);
input = reader;
}
SLDParser p = createSld10Parser(input, resourceLocator, entityResolver);
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;
} finally {
IOUtils.closeQuietly(reader);
}
}
StyledLayerDescriptor parse11(Object input, ResourceLocator resourceLocator, EntityResolver entityResolver)
throws IOException {
Parser parser = createSld11Parser(input, resourceLocator, entityResolver);
try(Reader reader = toReader(input)) {
parser.setEntityResolver(entityResolver);
return (StyledLayerDescriptor) parser.parse(reader);
}
catch(Exception e) {
throw new IOException(e);
}
}
SLDParser createSld10Parser(Object input, ResourceLocator resourceLocator, EntityResolver entityResolver)
throws IOException {
SLDParser parser;
if (input instanceof File) {
parser = new SLDParser(styleFactory, (File) input);
}
else {
parser = new SLDParser(styleFactory, toReader(input));
}
if (resourceLocator != null) {
parser.setOnLineResourceLocator(resourceLocator);
}
if (entityResolver != null) {
parser.setEntityResolver(entityResolver);
}
return parser;
}
Parser createSld11Parser(Object input, ResourceLocator resourceLocator, EntityResolver entityResolver) {
if (resourceLocator == null && input instanceof File) {
// setup for resolution of relative paths
final java.net.URL surl = DataUtilities.fileToURL((File) input);
DefaultResourceLocator defResourceLocator = new DefaultResourceLocator();
defResourceLocator.setSourceUrl(surl);
resourceLocator = defResourceLocator;
}
final ResourceLocator locator = resourceLocator;
SLDConfiguration sld;
if (locator != null) {
sld = new SLDConfiguration() {
protected void configureContext(org.picocontainer.MutablePicoContainer container) {
container.registerComponentInstance(ResourceLocator.class, locator);
};
};
}
else {
sld = new SLDConfiguration();
}
Parser parser = new Parser(sld);
if (entityResolver != null) {
parser.setEntityResolver(entityResolver);
}
return parser;
}
@Override
public void encode(StyledLayerDescriptor sld, Version version, boolean pretty, OutputStream output) throws IOException {
if (version != null && VERSION_11.compareTo(version) == 0) {
encode11(sld, pretty, output);
}
else {
encode10(sld, pretty, output);
}
}
void encode10(StyledLayerDescriptor sld, boolean pretty, OutputStream output)
throws IOException {
SLDTransformer tx = new SLDTransformer();
if (pretty) {
tx.setIndentation(2);
}
try {
tx.transform( sld, output );
}
catch (TransformerException e) {
throw (IOException) new IOException("Error writing style").initCause(e);
}
}
void encode11(StyledLayerDescriptor sld, boolean pretty, OutputStream output) throws IOException {
Encoder e = new Encoder(new SLDConfiguration());
e.setIndenting(pretty);
e.encode(sld, SLD.StyledLayerDescriptor, output);
}
@Override
public List<Exception> validate(Object input, Version version, EntityResolver entityResolver) throws IOException {
if (version == null) {
Object[] versionAndReader = getVersionAndReader(input);
version = (Version) versionAndReader[0];
input = versionAndReader[1];
}
if (version != null && VERSION_11.compareTo(version) == 0) {
return validate11(input, entityResolver);
}
else {
return validate10(input, entityResolver);
}
}
List<Exception> validate10(Object input, EntityResolver entityResolver) throws IOException {
try(Reader reader = toReader(input)) {
final SLDValidator validator = new SLDValidator();
validator.setEntityResolver(entityResolver);
return validator.validateSLD(new InputSource(reader));
}
}
List<Exception> validate11(Object input, EntityResolver entityResolver) throws IOException {
Parser p = createSld11Parser(input, null, entityResolver);
try (Reader reader = toReader(input)) {
p.validate(reader);
return p.getValidationErrors();
} catch(Exception e) {
throw new IOException(e);
}
}
@Override
public Version version(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.
*/
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, XML_LOOKAHEAD);
}
else {
reader = RequestUtils.getBufferedXMLReader(toReader(input), XML_LOOKAHEAD);
}
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};
}
}