it.geosolutions.geobatch.actions.ds2ds.geoserver.DSGeoServerAction.java Source code

Java tutorial

Introduction

Here is the source code for it.geosolutions.geobatch.actions.ds2ds.geoserver.DSGeoServerAction.java

Source

/*
 *  GeoBatch - Open Source geospatial batch processing system
 *  http://geobatch.geo-solutions.it/
 *  Copyright (C) 2007-2012 GeoSolutions S.A.S.
 *  http://www.geo-solutions.it
 *
 *  GPLv3 + Classpath exception
 *
 *  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 it.geosolutions.geobatch.actions.ds2ds.geoserver;

import it.geosolutions.filesystemmonitor.monitor.FileSystemEvent;
import it.geosolutions.geobatch.actions.ds2ds.dao.FeatureConfiguration;
import it.geosolutions.geobatch.annotations.Action;
import it.geosolutions.geobatch.annotations.CheckConfiguration;
import it.geosolutions.geobatch.flow.event.action.ActionException;
import it.geosolutions.geobatch.flow.event.action.BaseAction;
import it.geosolutions.geoserver.rest.GeoServerRESTManager;
import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder;
import it.geosolutions.geoserver.rest.encoder.GSResourceEncoder.ProjectionPolicy;
import it.geosolutions.geoserver.rest.encoder.datastore.GSOracleNGDatastoreEncoder;
import it.geosolutions.geoserver.rest.encoder.datastore.GSPostGISDatastoreEncoder;
import it.geosolutions.geoserver.rest.encoder.feature.GSFeatureTypeEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.EventObject;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.simple.SimpleFeatureSource;
import org.opengis.feature.type.GeometryDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;

/**
 * A GB action to publish DB layers in GeoServer, taking as event input the xml
 * output of the Ds2dsAction. The action also allows to create GeoServer
 * workspace and configure datastore if they do not exist yet.
 * 
 * @author Emmanuel Blondel (FAO) - emmanuel.blondel1@gmail.com |
 *         emmanuel.blondel@fao.org
 */
@Action(configurationClass = DSGeoServerConfiguration.class)
public class DSGeoServerAction extends BaseAction<EventObject> {

    protected final static Logger LOGGER = LoggerFactory.getLogger(DSGeoServerAction.class);

    private static final String acceptedFileType = "xml";

    final DSGeoServerConfiguration conf;

    /**
     * Constructs the DSGeoServerAction
     * 
     * @param actionConfiguration
     */
    public DSGeoServerAction(DSGeoServerConfiguration actionConfiguration) {
        super(actionConfiguration);
        this.conf = actionConfiguration;
    }

    @Override
    @CheckConfiguration
    public boolean checkConfiguration() {
        LOGGER.info("Calculating if this action could be Created...");
        return true;
    }

    @Override
    public Queue<EventObject> execute(Queue<EventObject> events) throws ActionException {

        listenerForwarder.started();

        // return object
        final Queue<EventObject> outputEvents = new LinkedList<EventObject>();

        //check global configurations
        //Geoserver config
        //----------------
        updateTask("Check GeoServer configuration");

        final String url = conf.getGeoserverURL();
        final String user = conf.getGeoserverUID();
        final String password = conf.getGeoserverPWD();
        GeoServerRESTManager gsMan = null;
        try {
            gsMan = new GeoServerRESTManager(new URL(url), user, password);
        } catch (MalformedURLException e) {
            failAction("Wrong GeoServer URL");

        } catch (IllegalArgumentException e) {
            failAction("Unable to create the GeoServer Manager using a null argument");

        }
        //TODO how to check if GS user/password are correct?
        listenerForwarder.progressing(5, "GeoServer configuration checked");

        //Check operation
        //---------------
        updateTask("Check operation");
        String op = conf.getOperation();
        if (op == null || !(op.equalsIgnoreCase("PUBLISH") || op.equalsIgnoreCase("REMOVE"))) {
            failAction("Bad operation: " + op + " in configuration");
        }
        listenerForwarder.progressing(10, "Operation checked");

        //Check WorkSpace
        //---------------
        updateTask("Check workspace configuration");
        String ws = conf.getDefaultNamespace();
        String wsUri = conf.getDefaultNamespaceUri();

        Boolean existWS = false;
        synchronized (existWS) {
            existWS = gsMan.getReader().getWorkspaceNames().contains(ws);

            if (!existWS) {

                boolean createWS = conf.getCreateNameSpace();
                if (createWS) {
                    //try to create the workspace
                    updateTask("Create workspace " + ws + " in GeoServer");
                    boolean created = false;
                    if (wsUri == null) {
                        created = gsMan.getPublisher().createWorkspace(ws);
                    } else {
                        try {
                            created = gsMan.getPublisher().createWorkspace(ws, new URI(wsUri));
                        } catch (URISyntaxException e) {
                            failAction("Invalid NameSpace URI " + wsUri + " in configuration");
                        }
                    }
                    if (!created) {
                        failAction("FATAL: unable to create workspace " + ws + " in GeoServer");
                    }
                } else {
                    failAction("Bad workspace (namespace): " + ws + " in configuration");
                }
            }
        }

        listenerForwarder.progressing(25, "GeoServer workspace checked");

        //event-based business logic
        while (events.size() > 0) {
            final EventObject ev;
            try {
                if ((ev = events.remove()) != null) {

                    updateTask("Working on incoming event: " + ev.getSource());

                    updateTask("Check acceptable file");
                    FileSystemEvent fileEvent = (FileSystemEvent) ev;

                    //set FeatureConfiguration
                    updateTask("Set Feature Configuration");
                    this.createFeatureConfiguration(fileEvent);
                    FeatureConfiguration featureConfig = conf.getFeatureConfiguration();

                    //TODO check FeatureConfiguration
                    updateTask("Check Feature Configuration");
                    if (featureConfig.getTypeName() == null) {
                        failAction("feature typeName cannot be null");
                    }

                    //TODO check if the typeName already exists for the target workspace?

                    //datastore check (and eventually creation)
                    updateTask("Check datastore configuration");
                    String ds = conf.getStoreName();

                    Boolean existDS = false;
                    synchronized (existDS) {

                        existDS = gsMan.getReader().getDatastores(ws).getNames().contains(ds);
                        if (!existDS) {
                            boolean createDS = conf.getCreateDataStore();
                            if (createDS) {

                                //create datastore
                                updateTask("Create datastore in GeoServer");
                                Map<String, Object> datastore = this.deserialize(featureConfig.getDataStore());

                                String dbType = (String) datastore.get("dbtype");

                                boolean created = false;
                                if (dbType.equalsIgnoreCase("postgis")) {
                                    GSPostGISDatastoreEncoder encoder = new GSPostGISDatastoreEncoder(ds);
                                    encoder.setName(ds);
                                    encoder.setEnabled(true);
                                    encoder.setHost((String) datastore.get("host"));
                                    encoder.setPort(Integer.parseInt((String) datastore.get("port")));
                                    encoder.setDatabase((String) datastore.get("database"));
                                    encoder.setSchema((String) datastore.get("schema"));
                                    encoder.setUser((String) datastore.get("user"));
                                    encoder.setPassword((String) datastore.get("passwd"));

                                    created = gsMan.getStoreManager().create(ws, encoder);
                                    if (!created) {
                                        failAction("FATAL: unable to create PostGIS datastore " + ds
                                                + " in GeoServer");
                                    }

                                } else if (dbType.equalsIgnoreCase("oracle")) {
                                    String dbname = (String) datastore.get("database");
                                    GSOracleNGDatastoreEncoder encoder = new GSOracleNGDatastoreEncoder(ds, dbname);
                                    encoder.setName(ds);
                                    encoder.setEnabled(true);
                                    encoder.setHost((String) datastore.get("host"));
                                    encoder.setPort(Integer.parseInt((String) datastore.get("port")));
                                    encoder.setDatabase(dbname);
                                    encoder.setSchema((String) datastore.get("schema"));
                                    encoder.setUser((String) datastore.get("user"));
                                    encoder.setPassword((String) datastore.get("passwd"));

                                    created = gsMan.getStoreManager().create(ws, encoder);
                                    if (!created) {
                                        failAction("FATAL: unable to create Oracle NG datastore " + ds
                                                + " in GeoServer");
                                    }
                                } else {
                                    failAction("The datastore type " + dbType + " is not supported");
                                }

                            } else {
                                failAction("Bad datastore:" + ds + " in configuration. Datastore " + ds
                                        + " doesn't exist in workspace (namespace) " + ws);
                            }
                        }
                    }
                    listenerForwarder.progressing(50, "Check GeoServer datastore");

                    //feature type publication/removal
                    boolean done = false;
                    if (op.equalsIgnoreCase("PUBLISH")) {
                        if (!gsMan.getReader().getLayers().getNames().contains(featureConfig.getTypeName())) {

                            updateTask("Publish DBLayer " + featureConfig.getTypeName() + " in GeoServer");

                            //featuretype
                            final GSFeatureTypeEncoder fte = new GSFeatureTypeEncoder();
                            fte.setName(featureConfig.getTypeName());
                            fte.setTitle(featureConfig.getTypeName());
                            String crs = featureConfig.getCrs();
                            if (crs != null) {
                                fte.setSRS(featureConfig.getCrs());
                            } else {
                                fte.setSRS("EPSG:4326");
                            }
                            fte.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);

                            //layer & styles
                            final GSLayerEncoder layerEncoder = new GSLayerEncoder();
                            layerEncoder.setDefaultStyle(this.defineLayerStyle(featureConfig, gsMan)); //default style

                            if (conf.getStyles() != null) {
                                //add available styles
                                for (String style : conf.getStyles()) {
                                    layerEncoder.addStyle(style);
                                }
                            }

                            //publish
                            done = gsMan.getPublisher().publishDBLayer(ws, ds, fte, layerEncoder);
                            if (!done) {
                                failAction("Impossible to publish DBLayer " + featureConfig.getTypeName()
                                        + " in GeoServer");
                            }
                        }

                    } else if (op.equalsIgnoreCase("REMOVE")) {
                        if (gsMan.getReader().getLayers().getNames().contains(featureConfig.getTypeName())) {

                            //remove
                            updateTask("Remove DBLayer " + featureConfig.getTypeName() + " from GeoServer");

                            done = gsMan.getPublisher().unpublishFeatureType(ws, ds, featureConfig.getTypeName());
                            if (!done) {
                                failAction("Impossible to remove DBLayer " + featureConfig.getTypeName()
                                        + " in GeoServer");
                            }
                        }
                    }

                    listenerForwarder.progressing(100F, "Successful Geoserver " + op + " operation");
                    listenerForwarder.completed();
                    outputEvents.add(ev);

                } else {
                    if (LOGGER.isErrorEnabled()) {
                        LOGGER.error("Encountered a NULL event: SKIPPING...");
                    }
                    continue;
                }
            } catch (Exception ioe) {
                failAction("Unable to produce the output: " + ioe.getLocalizedMessage(), ioe);
            }
        }
        return outputEvents;

    }

    /**
     * Creates the source DataStore from the given input file event.
     * 
     * @param fileEvent
     * @return
     * @throws IOException
     * @throws ActionException 
     */
    private void createFeatureConfiguration(FileSystemEvent fileEvent) throws IOException, ActionException {

        String fileType = FilenameUtils.getExtension(fileEvent.getSource().getName()).toLowerCase();
        FeatureConfiguration featureConfig = conf.getFeatureConfiguration();
        if (fileType.equals(acceptedFileType)) {
            InputStream inputXML = null;
            try {
                inputXML = new FileInputStream(fileEvent.getSource());
                featureConfig = FeatureConfiguration.fromXML(inputXML);
            } catch (Exception e) {
                throw new IOException("Unable to load input XML", e);
            } finally {
                IOUtils.closeQuietly(inputXML);
            }
        } else {
            failAction(
                    "Bad input file extension: " + fileEvent.getSource().getName() + ". Input must be an XML file");

        }
        conf.setFeatureConfiguration(featureConfig);
    }

    /**
     * Get the geometry type binding of the given layer
     * 
     * @param connect
     * @throws ActionException
     * @throws IOException
     */
    private Class<?> getGeometryTypeBinding(FeatureConfiguration featureConfig) throws ActionException {
        DataStore datastore = null;
        Class<?> binding = null;

        try {
            datastore = DataStoreFinder.getDataStore(featureConfig.getDataStore());
            SimpleFeatureSource sfs = datastore.getFeatureSource(featureConfig.getTypeName());
            GeometryDescriptor geomDescriptor = sfs.getSchema().getGeometryDescriptor();
            if (geomDescriptor != null) {
                binding = geomDescriptor.getType().getBinding();
            }
        } catch (IOException ioe) {
            failAction(ioe.getMessage());

        } finally {
            datastore.dispose();
        }

        return binding;
    }

    /**
     * Defines the style to set for the GeoServer layer. The method:
     * - first check for style availability in the configuration
     * - else it tries to assign a default style by checking the geometry type binding from the feature
     * configuration, checking the availability of Geoserver default styles, and eventually publish them
     * 
     * NOTE: this method was put in place, because in geoserver-manager, while no style assignment
     * makes GeoServer choosing the appropriate default style (polygon, style or point), it can result
     * in an GeoServer error when trying to access the Layer Publication panel.
     * 
     * @return
     * @throws ActionException 
     * @throws IOException 
     */
    private String defineLayerStyle(FeatureConfiguration featureConfig, GeoServerRESTManager gsMan)
            throws ActionException, IOException {
        updateTask("Define the layer default style");

        String defaultStyle = conf.getDefaultStyle();
        List<String> styles = gsMan.getReader().getStyles().getNames();
        if (defaultStyle != null) {
            updateTask("Use default style from configuration");
            if (!styles.contains(defaultStyle)) {
                failAction("Style " + defaultStyle + " doesn't exist in GeoServer");
            }
        } else {
            Class<?> geomBinding = this.getGeometryTypeBinding(featureConfig);
            if (geomBinding != null) {
                if (geomBinding.equals(Polygon.class) || geomBinding.equals(MultiPolygon.class)) {
                    defaultStyle = "polygon";
                } else if (geomBinding.equals(LineString.class) || geomBinding.equals(MultiLineString.class)) {
                    defaultStyle = "line";
                } else {
                    defaultStyle = "point";
                }
            } else {
                defaultStyle = "point";
            }
        }

        return defaultStyle;
    }

    /**
     * Deserializes datastore params. This method is used prior to the datastore
     * configuration using the geoserver-manager library
     * 
     * @param datastore
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private Map<String, Object> deserialize(Map<String, Serializable> datastore)
            throws IOException, ClassNotFoundException {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(datastore);
        objOut.close();
        ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
        Map<String, Object> connect = (Map<String, Object>) objIn.readObject();
        return connect;
    }

    /**
     * From ds2ds action
     * 
     * @param message
     * @param t
     * @throws ActionException
     */
    private void failAction(String message, Throwable t) throws ActionException {
        if (LOGGER.isErrorEnabled()) {
            LOGGER.error(message);
            if (t != null) {
                LOGGER.error(getStackTrace(t));
            }
        }
        if (!conf.isFailIgnored()) {
            final ActionException e = new ActionException(this, message, t);
            listenerForwarder.failed(e);
            throw e;
        }
    }

    /**
     * From ds2ds action
     * 
     * @param message
     * @throws ActionException
     */
    private void failAction(String message) throws ActionException {
        failAction(message, null);
    }

    /**
     * From ds2ds action
     * 
     * @param t
     * @return
     */
    private String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        t.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    /**
     * From ds2ds action
     * 
     * @param task
     */
    private void updateTask(String task) {
        listenerForwarder.setTask(task);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(task);
        }
    }

}