nl.b3p.viewer.stripes.SldActionBean.java Source code

Java tutorial

Introduction

Here is the source code for nl.b3p.viewer.stripes.SldActionBean.java

Source

/*
 * Copyright (C) 2012-2013 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package nl.b3p.viewer.stripes;

import java.awt.Color;
import java.io.*;
import java.net.URL;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.validation.Validate;
import nl.b3p.viewer.config.app.ApplicationLayer;
import nl.b3p.viewer.config.services.Layer;
import nl.b3p.viewer.config.services.SimpleFeatureType;
import nl.b3p.viewer.config.services.StyleLibrary;
import nl.b3p.viewer.util.ChangeMatchCase;
import nl.b3p.viewer.util.FeatureToJson;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.emf.common.util.URI;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.styling.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.stripesstuff.stripersist.Stripersist;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Create a SLD using GeoTools.
 *
 * @author Matthijs Laan
 */
@UrlBinding("/action/sld")
@StrictBinding
public class SldActionBean implements ActionBean {
    private static final Log log = LogFactory.getLog(SldActionBean.class);

    private ActionBeanContext context;

    public static final String FORMAT_JSON = "json";
    public static final String FORMAT_XML = "xml";

    @Validate
    private Long id;

    @Validate
    private String layer;

    @Validate
    private String style;

    @Validate
    private String filter;

    @Validate
    private String color;

    @Validate
    private String commonAndFilter;

    @Validate
    private String commonOrFilter;

    @Validate
    private Boolean useRuleFilter = false;

    @Validate
    private String featureTypeName;

    @Validate
    private String format;

    @Validate
    private ApplicationLayer applicationLayer;

    private byte[] sldXml;
    private StyledLayerDescriptor newSld;
    private StyleFactory sldFactory;

    //<editor-fold defaultstate="collapsed" desc="getters and setters">
    @Override
    public ActionBeanContext getContext() {
        return context;
    }

    @Override
    public void setContext(ActionBeanContext context) {
        this.context = context;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFilter() {
        return filter;
    }

    public void setFilter(String filter) {
        this.filter = filter;
    }

    public String getFeatureTypeName() {
        return featureTypeName;
    }

    public void setFeatureTypeName(String featureTypeName) {
        this.featureTypeName = featureTypeName;
    }

    public String getLayer() {
        return layer;
    }

    public void setLayer(String layer) {
        this.layer = layer;
    }

    public String getStyle() {
        return style;
    }

    public void setStyle(String style) {
        this.style = style;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public ApplicationLayer getApplicationLayer() {
        return applicationLayer;
    }

    public void setApplicationLayer(ApplicationLayer appLayer) {
        this.applicationLayer = appLayer;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getCommonAndFilter() {
        return commonAndFilter;
    }

    public void setCommonAndFilter(String commonAndFilter) {
        this.commonAndFilter = commonAndFilter;
    }

    public String getCommonOrFilter() {
        return commonOrFilter;
    }

    public void setCommonOrFilter(String commonOrFilter) {
        this.commonOrFilter = commonOrFilter;
    }
    //</editor-fold>

    private void getSldXmlOrCreateNewSld() throws Exception {

        if (id != null) {
            StyleLibrary sld = Stripersist.getEntityManager().find(StyleLibrary.class, id);
            if (sld == null) {
                throw new IllegalArgumentException("Can't find SLD in Flamingo service registry with id " + id);
            }
            if (sld.getExternalUrl() == null) {
                sldXml = sld.getSldBody().getBytes("UTF8");
            } else {
                // retrieve external sld
                try {
                    InputStream externalSld = new URL(sld.getExternalUrl()).openStream();
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    IOUtils.copy(externalSld, bos);
                    externalSld.close();
                    bos.flush();
                    bos.close();
                    sldXml = bos.toByteArray();
                } catch (IOException e) {
                    throw new IOException("Error retrieving external SLD from URL " + sld.getExternalUrl(), e);
                }
            }
        } else {
            // No SLD from database or external SLD; create new empty SLD

            newSld = sldFactory.createStyledLayerDescriptor();

            FilterFactory2 filterFactory = CommonFactoryFinder.getFilterFactory2();
            String[] layers = null;
            String[] filters = null;
            String[] styles = null;
            String[] colors = null;

            if (layer != null) {
                layers = layer.split(",");
            }
            if (filter != null) {
                try {
                    JSONArray jsonFilters = new JSONArray(filter);
                    filters = new String[jsonFilters.length()];
                    for (int i = 0; i < jsonFilters.length(); i++) {
                        filters[i] = jsonFilters.getString(i);
                    }
                } catch (JSONException je) {
                    log.warn("error while parsing filters to JSON", je);
                    filters = filter.split(",");
                }

            }
            if (color != null) {
                colors = color.split(",");
            }
            if (style != null) {
                styles = style.split(",");
            }

            Filter andFilter = null;
            Filter orFilter = null;
            if (commonAndFilter != null) {
                //GeoServer encodes the sld url even if its a valid url
                if (commonAndFilter.indexOf("%") > 0) {
                    commonAndFilter = URI.decode(commonAndFilter);
                }
                andFilter = ECQL.toFilter(commonAndFilter);
            }
            if (commonOrFilter != null) {
                //GeoServer encodes the sld url even if its a valid url
                if (commonOrFilter.indexOf("%") > 0) {
                    commonOrFilter = URI.decode(commonOrFilter);
                }
                orFilter = ECQL.toFilter(commonOrFilter);
            }
            if (layers != null) {

                for (int i = 0; i < layers.length; i++) {
                    Filter filter = null;
                    if (filters != null && i < filters.length && !"none".equals(filters[i])
                            && filters[i].length() > 0) {
                        filter = ECQL.toFilter(filters[i]);
                    }
                    NamedLayer nl = sldFactory.createNamedLayer();
                    nl.setName(layers[i]);

                    newSld.addStyledLayer(nl);
                    //Combine filter with allAndFilter and allOrFilter                     
                    if (andFilter != null) {
                        if (filter == null) {
                            filter = andFilter;
                        } else {
                            filter = filterFactory.and(filter, andFilter);
                        }
                    }
                    if (orFilter != null) {
                        if (filter == null) {
                            filter = orFilter;
                        } else {
                            filter = filterFactory.or(filter, orFilter);
                        }
                    }
                    if (styles != null && i < styles.length && !"none".equals(styles[i])) {
                        NamedStyle ns = sldFactory.createNamedStyle();
                        ns.setName(styles[i]);
                        nl.addStyle(ns);
                    } else if (colors != null && i < colors.length) {
                        //create featureTypeStyle
                        FeatureTypeStyle fts = sldFactory.createFeatureTypeStyle();
                        Rule r = sldFactory.createRule();
                        if (useRuleFilter && filter != null) {
                            r.setFilter(filter);
                        }
                        PolygonSymbolizer ps = createPolygonSymbolizer(sldFactory, colors[i]);
                        r.symbolizers().add(ps);
                        fts.rules().add(r);
                        // add style to namedlayer
                        Style style = sldFactory.createStyle();
                        style.setDefault(true);
                        style.setName("default");
                        style.featureTypeStyles().add(fts);
                        nl.addStyle(style);
                    } else {
                        NamedStyle ns = sldFactory.createNamedStyle();
                        ns.setName("default");
                        nl.addStyle(ns);
                    }

                    //if no featuretypestyle (created with color) then make featuretypeconstraint
                    if (!useRuleFilter && filter != null) {
                        // XXX name should be a feature type name from DescribeLayer response
                        // use extra parameter...
                        FeatureTypeConstraint ftc = sldFactory.createFeatureTypeConstraint(layers[i], filter,
                                new Extent[] {});
                        nl.setLayerFeatureConstraints(new FeatureTypeConstraint[] { ftc });
                    }
                }
            }
        }
    }

    private static final String NS_SLD = "http://www.opengis.net/sld";
    private static final String NS_SE = "http://www.opengis.net/se";

    private void addFilterToExistingSld() throws Exception {
        Filter f = CQL.toFilter(filter);

        f = (Filter) f.accept(new ChangeMatchCase(false), null);

        if (featureTypeName == null) {
            featureTypeName = layer;
        }
        FeatureTypeConstraint ftc = sldFactory.createFeatureTypeConstraint(featureTypeName, f, new Extent[] {});

        if (newSld == null) {

            SLDTransformer sldTransformer = new SLDTransformer();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            sldTransformer.transform(ftc, bos);

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            DocumentBuilder db = dbf.newDocumentBuilder();

            Document sldXmlDoc = db.parse(new ByteArrayInputStream(sldXml));

            Document ftcDoc = db.parse(new ByteArrayInputStream(bos.toByteArray()));

            String sldVersion = sldXmlDoc.getDocumentElement().getAttribute("version");
            if ("1.1.0".equals(sldVersion)) {
                // replace sld:FeatureTypeName element generated by GeoTools
                // by se:FeatureTypeName
                NodeList sldFTNs = ftcDoc.getElementsByTagNameNS(NS_SLD, "FeatureTypeName");
                if (sldFTNs.getLength() == 1) {
                    Node sldFTN = sldFTNs.item(0);
                    Node seFTN = ftcDoc.createElementNS(NS_SE, "FeatureTypeName");
                    seFTN.setTextContent(sldFTN.getTextContent());
                    sldFTN.getParentNode().replaceChild(seFTN, sldFTN);
                }
            }

            // Ignore namespaces to tackle both SLD 1.0.0 and SLD 1.1.0
            // Add constraint to all NamedLayers, not only to the layer specified
            // in layers parameter

            NodeList namedLayers = sldXmlDoc.getElementsByTagNameNS(NS_SLD, "NamedLayer");
            for (int i = 0; i < namedLayers.getLength(); i++) {
                Node namedLayer = namedLayers.item(i);

                // Search where to insert the FeatureTypeConstraint from our ftcDoc

                // Insert LayerFeatureConstraints after sld:Name, se:Name or se:Description
                // and before sld:NamedStyle or sld:UserStyle so search backwards.
                // If we find an existing LayerFeatureConstraints, use that
                NodeList childs = namedLayer.getChildNodes();
                Node insertBefore = null;
                Node layerFeatureConstraints = null;
                int j = childs.getLength() - 1;
                do {
                    Node child = childs.item(j);

                    if ("LayerFeatureConstraints".equals(child.getLocalName())) {
                        layerFeatureConstraints = child;
                        break;
                    }
                    if ("Description".equals(child.getLocalName()) || "Name".equals(child.getLocalName())) {
                        break;
                    }
                    insertBefore = child;
                    j--;
                } while (j >= 0);
                Node featureTypeConstraint = sldXmlDoc.adoptNode(ftcDoc.getDocumentElement().cloneNode(true));
                if (layerFeatureConstraints == null) {
                    layerFeatureConstraints = sldXmlDoc.createElementNS(NS_SLD, "LayerFeatureConstraints");
                    layerFeatureConstraints.appendChild(featureTypeConstraint);
                    namedLayer.insertBefore(layerFeatureConstraints, insertBefore);
                } else {
                    layerFeatureConstraints.appendChild(featureTypeConstraint);
                }
            }

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer t = tf.newTransformer();
            DOMSource source = new DOMSource(sldXmlDoc);
            bos = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(bos);
            t.transform(source, result);
            sldXml = bos.toByteArray();
        }
    }

    private PolygonSymbolizer createPolygonSymbolizer(StyleFactory styleFactory, String color) {
        FilterFactory2 filterFactory = CommonFactoryFinder.getFilterFactory2();

        Color col = Color.GRAY;
        if (color.startsWith("#")) {
            col = new Color(Integer.parseInt(color.substring(1), 16));
        }
        // create a partially opaque outline stroke
        Stroke stroke = styleFactory.createStroke(filterFactory.literal(col), filterFactory.literal(1),
                filterFactory.literal(0.9));

        // create a partial opaque fill
        Fill fill = styleFactory.createFill(filterFactory.literal(col), filterFactory.literal(0.9));

        PolygonSymbolizer sym = styleFactory.createPolygonSymbolizer(stroke, fill, null);
        return sym;
    }

    @DefaultHandler
    public Resolution create() throws JSONException, UnsupportedEncodingException {
        JSONObject json = new JSONObject();
        json.put("success", Boolean.FALSE);
        String error = null;

        try {
            sldFactory = CommonFactoryFinder.getStyleFactory();

            getSldXmlOrCreateNewSld();

            if (newSld == null && filter != null) {
                addFilterToExistingSld();
            }

            if (newSld != null) {
                SLDTransformer sldTransformer = new SLDTransformer();
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                sldTransformer.transform(newSld, bos);
                sldXml = bos.toByteArray();
            }

        } catch (Exception e) {
            log.error(String.format("Error creating sld for layer=%s, style=%s, filter=%s, id=%d", layer, style,
                    filter, id), e);

            error = e.toString();
            if (e.getCause() != null) {
                error += "; cause: " + e.getCause().toString();
            }
        }

        if (error != null) {
            if (FORMAT_JSON.equals(format)) {
                json.put("error", error);
                return new StreamingResolution("application/json", new StringReader(json.toString()));
            } else {
                return new ErrorResolution(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
            }
        } else {
            if (FORMAT_JSON.equals(format)) {
                json.put("sld", new String(sldXml, "UTF8"));
                json.put("success", Boolean.TRUE);
                return new StreamingResolution("application/json", new StringReader(json.toString()));
            } else {
                return new StreamingResolution("text/xml", new ByteArrayInputStream(sldXml));
            }
        }
    }

    /*
     * Reformat the filter with the relations of this featureType
     */
    public Resolution transformFilter() throws JSONException {
        JSONObject json = new JSONObject();
        String error = null;
        try {
            json.put("success", Boolean.FALSE);
            if (filter != null && applicationLayer != null) {
                Layer layer = applicationLayer.getService().getLayer(applicationLayer.getLayerName());
                if (layer == null) {
                    error = "Layer not found";
                } else {
                    SimpleFeatureType sft = layer.getFeatureType();
                    Filter f = CQL.toFilter(filter);
                    f = (Filter) f.accept(new ChangeMatchCase(false), null);
                    f = FeatureToJson.reformatFilter(f, sft);
                    json.put("filter", CQL.toCQL(f));
                    json.put("success", Boolean.TRUE);
                }

            } else {
                error = "No filter to transform or no applicationlayer";
            }
        } catch (Exception e) {
            log.error("Error while reformating filter", e);
            error = e.toString();
        }
        if (error != null) {
            json.put("error", error);
        }
        return new StreamingResolution("application/json", new StringReader(json.toString()));
    }
}