Package org.geoserver.catalog

Source Code of org.geoserver.catalog.CatalogBuilder

/* Copyright (c) 2001 - 2008 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.catalog;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.measure.unit.Unit;

import org.geoserver.catalog.impl.ModificationProxy;
import org.geoserver.catalog.impl.ResourceInfoImpl;
import org.geoserver.catalog.impl.StoreInfoImpl;
import org.geoserver.catalog.impl.StyleInfoImpl;
import org.geoserver.data.util.CoverageStoreUtils;
import org.geoserver.data.util.CoverageUtils;
import org.geoserver.ows.util.ClassProperties;
import org.geoserver.ows.util.OwsUtils;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.data.FeatureSource;
import org.geotools.data.ows.CRSEnvelope;
import org.geotools.data.ows.Layer;
import org.geotools.data.wms.WebMapServer;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.Version;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

/**
* Builder class which provides convenience methods for interacting with the catalog.
* <p>
* Warning: this class is stateful, and is not meant to be accessed by multiple threads and should
* not be an member variable of another class.
* </p>
*
* @author Justin Deoliveira, OpenGEO
*
*/
public class CatalogBuilder {

    static final Logger LOGGER = Logging.getLogger(CatalogBuilder.class);

    /**
     * the catalog
     */
    Catalog catalog;

    /**
     * the current workspace
     */
    WorkspaceInfo workspace;

    /**
     * the current store
     */
    StoreInfo store;

    public CatalogBuilder(Catalog catalog) {
        this.catalog = catalog;
    }

    /**
     * Sets the workspace to be used when creating store objects.
     */
    public void setWorkspace(WorkspaceInfo workspace) {
        this.workspace = workspace;
    }

    /**
     * Sets the store to be used when creating resource objects.
     */
    public void setStore(StoreInfo store) {
        this.store = store;
    }

    /**
     * Updates a workspace with the properties of another.
     *
     * @param original
     *            The workspace being updated.
     * @param update
     *            The workspace containing the new values.
     */
    public void updateWorkspace(WorkspaceInfo original, WorkspaceInfo update) {
        update(original, update, WorkspaceInfo.class);
    }

    /**
     * Updates a namespace with the properties of another.
     *
     * @param original
     *            The namespace being updated.
     * @param update
     *            The namespace containing the new values.
     */
    public void updateNamespace(NamespaceInfo original, NamespaceInfo update) {
        update(original, update, NamespaceInfo.class);
    }

    /**
     * Updates a datastore with the properties of another.
     *
     * @param original
     *            The datastore being updated.
     * @param update
     *            The datastore containing the new values.
     */
    public void updateDataStore(DataStoreInfo original, DataStoreInfo update) {
        update(original, update, DataStoreInfo.class);
    }

    /**
     * Updates a wms store with the properties of another.
     *
     * @param original
     *            The wms store being updated.
     * @param update
     *            The wms store containing the new values.
     */
    public void updateWMSStore(WMSStoreInfo original, WMSStoreInfo update) {
        update(original, update, WMSStoreInfo.class);
    }

    /**
     * Updates a coveragestore with the properties of another.
     *
     * @param original
     *            The coveragestore being updated.
     * @param update
     *            The coveragestore containing the new values.
     */
    public void updateCoverageStore(CoverageStoreInfo original, CoverageStoreInfo update) {
        update(original, update, CoverageStoreInfo.class);
    }

    /**
     * Updates a feature type with the properties of another.
     *
     * @param original
     *            The feature type being updated.
     * @param update
     *            The feature type containing the new values.
     */
    public void updateFeatureType(FeatureTypeInfo original, FeatureTypeInfo update) {
        update(original, update, FeatureTypeInfo.class);
    }

    /**
     * Updates a coverage with the properties of another.
     *
     * @param original
     *            The coverage being updated.
     * @param update
     *            The coverage containing the new values.
     */
    public void updateCoverage(CoverageInfo original, CoverageInfo update) {
        update(original, update, CoverageInfo.class);
    }

    /**
     * Updates a WMS layer with the properties of another.
     *
     * @param original
     *            The wms layer being updated.
     * @param update
     *            The wms layer containing the new values.
     */
    public void updateWMSLayer(WMSLayerInfo original, WMSLayerInfo update) {
        update(original, update, WMSLayerInfo.class);
    }

    /**
     * Updates a layer with the properties of another.
     *
     * @param original
     *            The layer being updated.
     * @param update
     *            The layer containing the new values.
     */
    public void updateLayer(LayerInfo original, LayerInfo update) {
        update(original, update, LayerInfo.class);
    }

    /**
     * Updates a layer group with the properties of another.
     *
     * @param original
     *            The layer group being updated.
     * @param update
     *            The layer group containing the new values.
     */
    public void updateLayerGroup(LayerGroupInfo original, LayerGroupInfo update) {
        update(original, update, LayerGroupInfo.class);
    }

    /**
     * Updates a style with the properties of another.
     *
     * @param original
     *            The style being updated.
     * @param update
     *            The style containing the new values.
     */
    public void updateStyle(StyleInfo original, StyleInfo update) {
        update(original, update, StyleInfo.class);
    }

    /**
     * Update method which uses reflection to grab property values from one object and set them on
     * another.
     * <p>
     * Null values from the <tt>update</tt> object are ignored.
     * </p>
     */
    <T> void update(T original, T update, Class<T> clazz) {
        OwsUtils.copy(update, original, clazz);
    }

    /**
     * Builds a new data store.
     */
    public DataStoreInfo buildDataStore(String name) {
        DataStoreInfo info = catalog.getFactory().createDataStore();
        buildStore(info, name);

        return info;
    }

    /**
     * Builds a new coverage store.
     */
    public CoverageStoreInfo buildCoverageStore(String name) {
        CoverageStoreInfo info = catalog.getFactory().createCoverageStore();
        buildStore(info, name);

        return info;
    }

    /**
     * Builds a new WMS store
     */
    public WMSStoreInfo buildWMSStore(String name) throws IOException {
        WMSStoreInfo info = catalog.getFactory().createWebMapServer();
        buildStore(info, name);
        info.setType("WMS");

        return info;
    }

    /**
     * Builds a store.
     * <p>
     * The workspace of the resulting store is {@link #workspace} if set, else the default workspace
     * from the catalog.
     * </p>
     */
    void buildStore(StoreInfo info, String name) {

        info.setName(name);
        info.setEnabled(true);

        // set workspace, falling back on default if none specified
        if (workspace != null) {
            info.setWorkspace(workspace);
        } else {
            info.setWorkspace(catalog.getDefaultWorkspace());
        }
    }

    /**
     * Builds a {@link FeatureTypeInfo} from the current datastore and the specified type name
     * <p>
     * The resulting object is not added to the catalog, it must be done by the calling code after
     * the fact.
     * </p>
     */
    public FeatureTypeInfo buildFeatureType(Name typeName) throws Exception {
        if (store == null || !(store instanceof DataStoreInfo)) {
            throw new IllegalStateException("Data store not set.");
        }

        DataStoreInfo dstore = (DataStoreInfo) store;
        return buildFeatureType(dstore.getDataStore(null).getFeatureSource(typeName));
    }

    /**
     * Builds a feature type from a geotools feature source. The resulting {@link FeatureTypeInfo}
     * will still miss the bounds and might miss the SRS. Use {@link #lookupSRS(FeatureTypeInfo,
     * true)} and {@link #setupBounds(FeatureTypeInfo)} if you want to force them in (and spend time
     * accordingly)
     * <p>
     * The resulting object is not added to the catalog, it must be done by the calling code after
     * the fact.
     * </p>
     */
    public FeatureTypeInfo buildFeatureType(FeatureSource featureSource) {
        if (store == null || !(store instanceof DataStoreInfo)) {
            throw new IllegalStateException("Data store not set.");
        }

        FeatureType featureType = featureSource.getSchema();

        FeatureTypeInfo ftinfo = catalog.getFactory().createFeatureType();
        ftinfo.setStore(store);
        ftinfo.setEnabled(true);

        // naming
        ftinfo.setNativeName(featureType.getName().getLocalPart());
        ftinfo.setName(featureType.getName().getLocalPart());

        WorkspaceInfo workspace = store.getWorkspace();
        NamespaceInfo namespace = catalog.getNamespaceByPrefix(workspace.getName());
        if (namespace == null) {
            namespace = catalog.getDefaultNamespace();
        }

        ftinfo.setNamespace(namespace);

        CoordinateReferenceSystem crs = featureType.getCoordinateReferenceSystem();
        if (crs == null && featureType.getGeometryDescriptor() != null) {
            crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();
        }
        ftinfo.setNativeCRS(crs);

        // srs look and set (by default we just use fast lookup)
        try {
            lookupSRS(ftinfo, false);
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "SRS lookup failed", e);
        }
        setupProjectionPolicy(ftinfo);

        // quick metadata
        ftinfo.setTitle(featureType.getName().getLocalPart());

        return ftinfo;
    }

    /**
     * Sets the projection policy for a resource based on the following rules:
     * <ul>
     * <li>If getSRS() returns a non null value it is set to {@Link
     * ProjectionPolicy#FORCE_DECLARED}
     * <li>If getSRS() returns a null value it is set to {@link ProjectionPolicy#NONE}
     * </ul>
     *
     * TODO: make this method smarter, and compare the native crs to figure out if prejection
     * actually needs to be done, and sync it up with setting proj policy on coverage layers.
     */
    public void setupProjectionPolicy(ResourceInfo rinfo) {
        if (rinfo.getSRS() != null) {
            rinfo.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);
        } else {
            rinfo.setProjectionPolicy(ProjectionPolicy.NONE);
        }
    }

    /**
     * Given a {@link ResourceInfo} this method:
     * <ul>
     * <li>computes, if missing, the native bounds (warning, this might be very expensive, cases in
     * which this case take minutes are not uncommon if the data set is made of million of features)
     * </li>
     * <li>updates, if possible, the geographic bounds accordingly by re-projecting the native
     * bounds into WGS84</li>
     *
     * @param ftinfo
     * @throws IOException
     *             if computing the native bounds fails or if a transformation error occurs during
     *             the geographic bounds computation
     */
    public void setupBounds(ResourceInfo rinfo) throws IOException {
        // setup the native bbox if needed
        if (rinfo.getNativeBoundingBox() == null) {
            ReferencedEnvelope bounds = getNativeBounds(rinfo);
            rinfo.setNativeBoundingBox(bounds);
        }

        // setup the geographic bbox if missing and we have enough info
        rinfo.setLatLonBoundingBox(getLatLonBounds(rinfo.getNativeBoundingBox(), rinfo.getCRS()));
    }

    /**
     * Computes the geographic bounds of a {@link ResourceInfo} by reprojecting the available native
     * bounds
     *
     * @param rinfo
     * @return the geographic bounds, or null if the native bounds are not available
     * @throws IOException
     */
    public ReferencedEnvelope getLatLonBounds(ReferencedEnvelope nativeBounds,
            CoordinateReferenceSystem declaredCRS) throws IOException {
        if (nativeBounds != null && declaredCRS != null) {
            // make sure we use the declared CRS, not the native one, the may differ
            if (!CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, declaredCRS)) {
                // transform
                try {
                    ReferencedEnvelope bounds = new ReferencedEnvelope(nativeBounds, declaredCRS);
                    return bounds.transform(DefaultGeographicCRS.WGS84, true);
                } catch (Exception e) {
                    throw (IOException) new IOException("transform error").initCause(e);
                }
            } else {
                return new ReferencedEnvelope(nativeBounds, DefaultGeographicCRS.WGS84);
            }
        }
        return null;
    }

    /**
     * Computes the native bounds of a {@link ResourceInfo} taking into account the nature of the
     * data and the reprojection policy in act
     *
     * @param rinfo
     * @return the native bounds, or null if the could not be computed
     * @throws IOException
     */
    public ReferencedEnvelope getNativeBounds(ResourceInfo rinfo) throws IOException {
        ReferencedEnvelope bounds = null;
        if (rinfo instanceof FeatureTypeInfo) {
            FeatureTypeInfo ftinfo = (FeatureTypeInfo) rinfo;

            // bounds
            bounds = ftinfo.getFeatureSource(null, null).getBounds();

            // fix the native bounds if necessary, some datastores do
            // not build a proper referenced envelope
            CoordinateReferenceSystem crs = ftinfo.getNativeCRS();
            if (bounds != null && bounds.getCoordinateReferenceSystem() == null && crs != null) {
                bounds = new ReferencedEnvelope(bounds, crs);
            }

            if (bounds != null) {
                // expansion factor if the bounds are empty or one dimensional
                double expandBy = 1; // 1 meter
                if (bounds.getCoordinateReferenceSystem() instanceof GeographicCRS) {
                    expandBy = 0.0001;
                }
                if (bounds.getWidth() == 0 || bounds.getHeight() == 0) {
                    bounds.expandBy(expandBy);
                }
            }

        } else if (rinfo instanceof CoverageInfo) {
            // the coverage bounds computation path is a bit more linear, the
            // readers always return the bounds and in the proper CRS (afaik)
            CoverageInfo cinfo = (CoverageInfo) rinfo;           
            AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) cinfo.getGridCoverageReader(null, GeoTools.getDefaultHints());  

            // get  bounds
            bounds = new ReferencedEnvelope(reader.getOriginalEnvelope());
          
        }

        // apply the bounds, taking into account the reprojection policy if need be
        if (rinfo.getProjectionPolicy() == ProjectionPolicy.REPROJECT_TO_DECLARED && bounds != null) {
            try {
                bounds = bounds.transform(rinfo.getCRS(), true);
            } catch (Exception e) {
                throw (IOException) new IOException("transform error").initCause(e);
            }
        }

        return bounds;
    }

    /**
     * Looks up and sets the SRS based on the feature type info native
     * {@link CoordinateReferenceSystem}
     *
     * @param ftinfo
     * @param extensive
     *            if true an extenstive lookup will be performed (more accurate, but might take
     *            various seconds)
     * @throws IOException
     */
    public void lookupSRS(FeatureTypeInfo ftinfo, boolean extensive) throws IOException {
        CoordinateReferenceSystem crs = ftinfo.getNativeCRS();
        if (crs == null) {
            crs = ftinfo.getFeatureType().getCoordinateReferenceSystem();
        }
        if (crs != null) {
            try {
                Integer code = CRS.lookupEpsgCode(crs, extensive);
                if (code != null)
                    ftinfo.setSRS("EPSG:" + code);
            } catch (FactoryException e) {
                throw (IOException) new IOException().initCause(e);
            }
        }
    }

    /**
     * Initializes a feature type object setting any info that has not been set.
     */
    public void initFeatureType(FeatureTypeInfo featureType) throws Exception {
        if (featureType.getCatalog() == null) {
            featureType.setCatalog(catalog);
        }
        if (featureType.getNativeName() == null && featureType.getName() != null) {
            featureType.setNativeName(featureType.getName());
        }
        if (featureType.getNativeName() != null && featureType.getName() == null) {
            featureType.setName(featureType.getNativeName());
        }
        // setup the srs if missing
        if (featureType.getSRS() == null) {
            lookupSRS(featureType, true);
        }
        if (featureType.getProjectionPolicy() == null) {
            setupProjectionPolicy(featureType);
        }

        // deal with bounding boxes as possible
        CoordinateReferenceSystem crs = featureType.getCRS();
        if (featureType.getLatLonBoundingBox() == null
                && featureType.getNativeBoundingBox() == null) {
            // both missing, we compute them
            setupBounds(featureType);
        } else if (featureType.getLatLonBoundingBox() == null) {
            // native available but geographic to be computed
            setupBounds(featureType);
        } else if (featureType.getNativeBoundingBox() == null && crs != null) {
            // we know the geographic and we can reproject back to native
            ReferencedEnvelope boundsLatLon = featureType.getLatLonBoundingBox();
            featureType.setNativeBoundingBox(boundsLatLon.transform(crs, true));
        }
    }

    /**
     * Initializes a wms layer object setting any info that has not been set.
     */
    public void initWMSLayer(WMSLayerInfo wmsLayer) throws Exception {
        wmsLayer.setCatalog(catalog);
       
        // set the name
        if (wmsLayer.getNativeName() == null && wmsLayer.getName() != null) {
            wmsLayer.setNativeName(wmsLayer.getName());
        }
        if (wmsLayer.getNativeName() != null && wmsLayer.getName() == null) {
            wmsLayer.setName(wmsLayer.getNativeName());
        }
       
        // get a fully initialized version we can copy from
        WMSLayerInfo full = buildWMSLayer(wmsLayer.getNativeName());

        // setup the srs if missing
        if (wmsLayer.getSRS() == null) {
            wmsLayer.setSRS(full.getSRS());
        }
        if (wmsLayer.getProjectionPolicy() == null) {
            wmsLayer.setProjectionPolicy(full.getProjectionPolicy());
        }

        // deal with bounding boxes as possible
        if (wmsLayer.getLatLonBoundingBox() == null
                && wmsLayer.getNativeBoundingBox() == null) {
            // both missing, we copy them
            wmsLayer.setLatLonBoundingBox(full.getLatLonBoundingBox());
            wmsLayer.setNativeBoundingBox(full.getNativeBoundingBox());
        } else if (wmsLayer.getLatLonBoundingBox() == null) {
            // native available but geographic to be computed
            setupBounds(wmsLayer);
        } else if (wmsLayer.getNativeBoundingBox() == null && wmsLayer.getNativeCRS() != null) {
            // we know the geographic and we can reproject back to native
            ReferencedEnvelope boundsLatLon = wmsLayer.getLatLonBoundingBox();
            wmsLayer.setNativeBoundingBox(boundsLatLon.transform(wmsLayer.getNativeCRS(), true));
        }
    }

    /**
     * Builds the default coverage contained in the current store
     *
     * @return
     * @throws Exception
     */
    public CoverageInfo buildCoverage() throws Exception {
        if (store == null || !(store instanceof CoverageStoreInfo)) {
            throw new IllegalStateException("Coverage store not set.");
        }

        CoverageStoreInfo csinfo = (CoverageStoreInfo) store;
        AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) catalog
                .getResourcePool().getGridCoverageReader(csinfo, GeoTools.getDefaultHints());

        if (reader == null)
            throw new Exception("Unable to acquire a reader for this coverage with format: "
                    + csinfo.getFormat().getName());

        return buildCoverage(reader, null);
    }

    /**
     * Builds a coverage from a geotools grid coverage reader.
     * @param customParameters
     */
    public CoverageInfo buildCoverage(AbstractGridCoverage2DReader reader, Map customParameters) throws Exception {
        if (store == null || !(store instanceof CoverageStoreInfo)) {
            throw new IllegalStateException("Coverage store not set.");
        }

        CoverageStoreInfo csinfo = (CoverageStoreInfo) store;
        CoverageInfo cinfo = catalog.getFactory().createCoverage();

        cinfo.setStore(csinfo);
        cinfo.setEnabled(true);

        WorkspaceInfo workspace = store.getWorkspace();
        NamespaceInfo namespace = catalog.getNamespaceByPrefix(workspace.getName());
        if (namespace == null) {
            namespace = catalog.getDefaultNamespace();
        }
        cinfo.setNamespace(namespace);

        CoordinateReferenceSystem nativeCRS = reader.getCrs();
        cinfo.setNativeCRS(nativeCRS);

        // mind the default projection policy, Coverages do not have a flexible
        // handling as feature types, they do reproject if the native srs is set,
        // force if missing
        if (nativeCRS != null && !nativeCRS.getIdentifiers().isEmpty()) {
            cinfo.setSRS(nativeCRS.getIdentifiers().toArray()[0].toString());
            cinfo.setProjectionPolicy(ProjectionPolicy.REPROJECT_TO_DECLARED);
        }
        if (nativeCRS == null) {
            cinfo.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);
        }

        GeneralEnvelope envelope = reader.getOriginalEnvelope();
        cinfo.setNativeBoundingBox(new ReferencedEnvelope(envelope));
        cinfo.setLatLonBoundingBox(new ReferencedEnvelope(CoverageStoreUtils.getWGS84LonLatEnvelope(envelope)));

        GridEnvelope originalRange = reader.getOriginalGridRange();
        cinfo.setGrid(new GridGeometry2D(originalRange, reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER), nativeCRS));

        // /////////////////////////////////////////////////////////////////////
        //
        // Now reading a fake small GridCoverage just to retrieve meta
        // information about bands:
        //
        // - calculating a new envelope which is 1/20 of the original one
        // - reading the GridCoverage subset
        //
        // /////////////////////////////////////////////////////////////////////
        Format format = csinfo.getFormat();
        final GridCoverage2D gc;

        final ParameterValueGroup readParams = format.getReadParameters();
        final Map parameters = CoverageUtils.getParametersKVP(readParams);
        final int minX = originalRange.getLow(0);
        final int minY = originalRange.getLow(1);
        final int width = originalRange.getSpan(0);
        final int height = originalRange.getSpan(1);
        final int maxX = minX + (width <= 5 ? width : 5);
        final int maxY = minY + (height <= 5 ? height : 5);

        // we have to be sure that we are working against a valid grid range.
        final GridEnvelope2D testRange = new GridEnvelope2D(minX, minY, maxX, maxY);

        // build the corresponding envelope
        final MathTransform gridToWorldCorner = reader.getOriginalGridToWorld(PixelInCell.CELL_CORNER);
        final GeneralEnvelope testEnvelope = CRS.transform(gridToWorldCorner, new GeneralEnvelope(testRange.getBounds()));
        testEnvelope.setCoordinateReferenceSystem(nativeCRS);

        if (customParameters != null) {
          parameters.putAll(customParameters);
        }
       
        parameters.put(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString(), new GridGeometry2D(testRange, testEnvelope));

        // try to read this coverage
        gc = (GridCoverage2D) reader.read(CoverageUtils.getParameters(readParams, parameters, true));
        if (gc == null) {
            throw new Exception("Unable to acquire test coverage for format:" + format.getName());
        }

        // remove read grid geometry since it is request specific
        parameters.remove(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString());

        cinfo.getDimensions().addAll(getCoverageDimensions(gc.getSampleDimensions()));
        // TODO:
        // dimentionNames = getDimensionNames(gc);
        /*
         * StringBuilder cvName =null; int count = 0; while (true) { final StringBuilder key = new
         * StringBuilder(gc.getName().toString()); if (count > 0) { key.append("_").append(count); }
         *
         * Map coverages = dataConfig.getCoverages(); Set cvKeySet = coverages.keySet(); boolean
         * key_exists = false;
         *
         * for (Iterator it = cvKeySet.iterator(); it.hasNext();) { String cvKey = ((String)
         * it.next()).toLowerCase(); if (cvKey.endsWith(key.toString().toLowerCase())) { key_exists
         * = true; } }
         *
         * if (!key_exists) { cvName = key; break; } else { count++; } }
         *
         * String name = cvName.toString();
         */
        String name = gc.getName().toString();
        cinfo.setName(name);
        cinfo.setTitle(name);
        cinfo.setDescription(new StringBuilder("Generated from ").append(format.getName()).toString());

        // keywords
        cinfo.getKeywords().add("WCS");
        cinfo.getKeywords().add(format.getName());
        cinfo.getKeywords().add(name);

        // native format name
        cinfo.setNativeFormat(format.getName());
        cinfo.getMetadata().put("dirName", new StringBuilder(store.getName()).append("_").append(name).toString());

        // request SRS's
        if ((gc.getCoordinateReferenceSystem2D().getIdentifiers() != null)
                && !gc.getCoordinateReferenceSystem2D().getIdentifiers().isEmpty()) {
            cinfo.getRequestSRS().add(((Identifier) gc.getCoordinateReferenceSystem2D().getIdentifiers().toArray()[0]).toString());
        }

        // response SRS's
        if ((gc.getCoordinateReferenceSystem2D().getIdentifiers() != null)
                && !gc.getCoordinateReferenceSystem2D().getIdentifiers().isEmpty()) {
            cinfo.getResponseSRS().add(((Identifier) gc.getCoordinateReferenceSystem2D().getIdentifiers().toArray()[0]).toString());
        }

        // supported formats
        final List formats = CoverageStoreUtils.listDataFormats();
        for (Iterator i = formats.iterator(); i.hasNext();) {
            final Format fTmp = (Format) i.next();
            final String fName = fTmp.getName();

            if (fName.equalsIgnoreCase("WorldImage")) {
                // TODO check if coverage can encode Format
                cinfo.getSupportedFormats().add("GIF");
                cinfo.getSupportedFormats().add("PNG");
                cinfo.getSupportedFormats().add("JPEG");
                cinfo.getSupportedFormats().add("TIFF");
            } else if (fName.toLowerCase().startsWith("geotiff")) {
                // TODO check if coverage can encode Format
                cinfo.getSupportedFormats().add("GEOTIFF");
            } else {
                // TODO check if coverage can encode Format
                cinfo.getSupportedFormats().add(fName);
            }
        }

        // interpolation methods
        cinfo.setDefaultInterpolationMethod("nearest neighbor");
        cinfo.getInterpolationMethods().add("nearest neighbor");
        cinfo.getInterpolationMethods().add("bilinear");
        cinfo.getInterpolationMethods().add("bicubic");

        // read parameters
        cinfo.getParameters().putAll(/*CoverageUtils.getParametersKVP(format.getReadParameters())*/ parameters);

        /// dispose coverage
        gc.dispose(true);

        return cinfo;
    }

    List<CoverageDimensionInfo> getCoverageDimensions(GridSampleDimension[] sampleDimensions) {

        final int length = sampleDimensions.length;
        List<CoverageDimensionInfo> dims = new ArrayList<CoverageDimensionInfo>();

        for (int i = 0; i < length; i++) {
            CoverageDimensionInfo dim = catalog.getFactory().createCoverageDimension();
            dim.setName(sampleDimensions[i].getDescription().toString(Locale.getDefault()));

            StringBuilder label = new StringBuilder("GridSampleDimension".intern());
            final Unit uom = sampleDimensions[i].getUnits();

            if (uom != null) {
                label.append("(".intern());
                parseUOM(label, uom);
                label.append(")".intern());
            }

            label.append("[".intern());
            label.append(sampleDimensions[i].getMinimumValue());
            label.append(",".intern());
            label.append(sampleDimensions[i].getMaximumValue());
            label.append("]".intern());

            dim.setDescription(label.toString());
            dim.setRange(sampleDimensions[i].getRange());

            final List<Category> categories = sampleDimensions[i].getCategories();
            if (categories != null) {
                for (Category cat : categories) {

                    if ((cat != null) && cat.getName().toString().equalsIgnoreCase("no data")) {
                        double min = cat.getRange().getMinimum();
                        double max = cat.getRange().getMaximum();

                        dim.getNullValues().add(min);
                        if (min != max) {
                            dim.getNullValues().add(max);
                        }
                    }
                }
            }

            dims.add(dim);
        }

        return dims;
    }

    public WMSLayerInfo buildWMSLayer(String layerName) throws IOException {
        if (store == null || !(store instanceof WMSStoreInfo)) {
            throw new IllegalStateException("WMS store not set.");
        }

        WMSStoreInfo wms = (WMSStoreInfo) store;
        WMSLayerInfo wli = catalog.getFactory().createWMSLayer();
        wli.setName(layerName);
        wli.setNativeName(layerName);

        wli.setStore(store);
        wli.setEnabled(true);

        WorkspaceInfo workspace = store.getWorkspace();
        NamespaceInfo namespace = catalog.getNamespaceByPrefix(workspace.getName());
        if (namespace == null) {
            namespace = catalog.getDefaultNamespace();
        }
        wli.setNamespace(namespace);

        Layer layer = wli.getWMSLayer(null);

        // try to get the native SRS -> we use the bounding boxes, GeoServer will publish all of the
        // supported SRS in the root, if we use getSRS() we'll get them all
        for (String srs : layer.getBoundingBoxes().keySet()) {
            try {
                CoordinateReferenceSystem crs = CRS.decode(srs);
                wli.setSRS(srs);
                wli.setNativeCRS(crs);
            } catch (Exception e) {
                LOGGER.log(Level.INFO, "Skipping " + srs
                        + " definition, it was not recognized by the referencing subsystem");
            }
        }
        // fall back on WGS84 if necessary
        if (wli.getSRS() == null) {
            wli.setSRS("EPSG:4326");
            wli.setNativeCRS(DefaultGeographicCRS.WGS84);
        }
        wli.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);

        // try to grab the envelope
        GeneralEnvelope envelope = layer.getEnvelope(wli.getNativeCRS());
        if (envelope != null) {
            ReferencedEnvelope re = new ReferencedEnvelope(envelope.getMinimum(0), envelope
                    .getMaximum(0), envelope.getMinimum(1), envelope.getMaximum(1), wli
                    .getNativeCRS());
            // are we in crazy wms 1.3 land?
            if(wli.getNativeCRS() instanceof GeographicCRS) {
                WebMapServer mapServer = wms.getWebMapServer(null);
                Version version = new Version(mapServer.getCapabilities().getVersion());
                if(version.compareTo(new Version("1.3.0")) >= 0) {
                    // flip axis, the wms code won't actually use the crs
                    double minx = re.getMinX();
                    double miny = re.getMinY();
                    double maxx = re.getMaxX();
                    double maxy = re.getMaxY();
                    re = new ReferencedEnvelope(miny, maxy, minx, maxx, wli.getNativeCRS());
                }
            }
            wli.setNativeBoundingBox(re);
        }
        CRSEnvelope llbbox = layer.getLatLonBoundingBox();
        if (llbbox != null) {
            ReferencedEnvelope re = new ReferencedEnvelope(llbbox.getMinX(), llbbox.getMaxX(),
                    llbbox.getMinY(), llbbox.getMaxY(), DefaultGeographicCRS.WGS84);
            wli.setLatLonBoundingBox(re);
        } else if (wli.getNativeBoundingBox() != null) {
            try {
                wli.setLatLonBoundingBox(wli.getNativeBoundingBox().transform(
                        DefaultGeographicCRS.WGS84, true));
            } catch (Exception e) {
                LOGGER.log(Level.INFO, "Could not transform native bbox into a lat/lon one", e);
            }
        }

        // reflect all the metadata that we can grab
        wli.setAbstract(layer.get_abstract());
        wli.setDescription(layer.get_abstract());
        wli.setTitle(layer.getTitle());
        if (layer.getKeywords() != null) {
            wli.getKeywords().addAll(Arrays.asList(layer.getKeywords()));
        }

        // strip off the prefix if we're cascading from a server that does add them
        String published = wli.getName();
        if (published.contains(":")) {
            wli.setName(published.substring(published.lastIndexOf(':') + 1));
        }

        return wli;
    }

    void parseUOM(StringBuilder label, Unit uom) {
        String uomString = uom.toString();
        uomString = uomString.replaceAll("�", "^2");
        uomString = uomString.replaceAll("�", "^3");
        uomString = uomString.replaceAll("�", "A");
        uomString = uomString.replaceAll("�", "");
        label.append(uomString);
    }

    /**
     * Builds a layer for a feature type.
     * <p>
     * The resulting object is not added to the catalog, it must be done by the calling code after
     * the fact.
     * </p>
     */
    public LayerInfo buildLayer(FeatureTypeInfo featureType) throws IOException {
        // also create a layer for the feautre type
        LayerInfo layer = buildLayer((ResourceInfo) featureType);

        StyleInfo style = getDefaultStyle(featureType);
        layer.setDefaultStyle(style);

        return layer;
    }

    /**
     * Builds a layer for a coverage.
     * <p>
     * The resulting object is not added to the catalog, it must be done by the calling code after
     * the fact.
     * </p>
     */
    public LayerInfo buildLayer(CoverageInfo coverage) throws IOException {
        LayerInfo layer = buildLayer((ResourceInfo) coverage);

        layer.setDefaultStyle(getDefaultStyle(coverage));

        return layer;
    }

    /**
     * Builds a layer wrapping a WMS layer resource
     * <p>
     * The resulting object is not added to the catalog, it must be done by the calling code after
     * the fact.
     * </p>
     */
    public LayerInfo buildLayer(WMSLayerInfo wms) throws IOException {
        LayerInfo layer = buildLayer((ResourceInfo) wms);
       
        layer.setDefaultStyle(getDefaultStyle(wms));
       
        return layer;
    }

    /**
     * Returns the default style for the specified resource, or null if the layer is vector and
     * geometryless
     *
     * @param resource
     * @return
     * @throws IOException
     */
    public StyleInfo getDefaultStyle(ResourceInfo resource) throws IOException {
        // raster wise, only one style
        if (resource instanceof CoverageInfo || resource instanceof WMSLayerInfo)
            return catalog.getStyleByName(StyleInfo.DEFAULT_RASTER);

        // for vectors we depend on the the nature of the default geometry
        String styleName;
        FeatureTypeInfo featureType = (FeatureTypeInfo) resource;
        GeometryDescriptor gd = featureType.getFeatureType().getGeometryDescriptor();
        if (gd == null)
            return null;

        Class gtype = gd.getType().getBinding();
        if (Point.class.isAssignableFrom(gtype) || MultiPoint.class.isAssignableFrom(gtype)) {
            styleName = StyleInfo.DEFAULT_POINT;
        } else if (LineString.class.isAssignableFrom(gtype)
                || MultiLineString.class.isAssignableFrom(gtype)) {
            styleName = StyleInfo.DEFAULT_LINE;
        } else if (Polygon.class.isAssignableFrom(gtype)
                || MultiPolygon.class.isAssignableFrom(gtype)) {
            styleName = StyleInfo.DEFAULT_POLYGON;
        } else {
            // fall back to point
            styleName = StyleInfo.DEFAULT_POINT;
        }

        return catalog.getStyleByName(styleName);
    }

    LayerInfo buildLayer(ResourceInfo resource) {
        LayerInfo layer = catalog.getFactory().createLayer();
        layer.setResource(resource);
        layer.setName(resource.getName());
        layer.setEnabled(true);

        // setup the layer type
        if (layer.getResource() instanceof FeatureTypeInfo) {
            layer.setType(LayerInfo.Type.VECTOR);
        } else if (layer.getResource() instanceof CoverageInfo) {
            layer.setType(LayerInfo.Type.RASTER);
        } else if (layer.getResource() instanceof WMSLayerInfo) {
            layer.setType(LayerInfo.Type.WMS);
        }

        return layer;
    }

    /**
     * Calculates the bounds of a layer group specifying a particular crs.
     */
    public void calculateLayerGroupBounds(LayerGroupInfo lg, CoordinateReferenceSystem crs)
            throws Exception {

        if (lg.getLayers().isEmpty()) {
            return;
        }

        LayerInfo l = lg.getLayers().get(0);
        ReferencedEnvelope bounds = transform(l.getResource().getLatLonBoundingBox(), crs);

        for (int i = 1; i < lg.getLayers().size(); i++) {
            l = lg.getLayers().get(i);
            bounds.expandToInclude(transform(l.getResource().getLatLonBoundingBox(), crs));
        }
        lg.setBounds(bounds);
    }

    /**
     * Calculates the bounds of a layer group by aggregating the bounds of each layer. TODO: move
     * this method to a utility class, it should not be on a builder.
     */
    public void calculateLayerGroupBounds(LayerGroupInfo lg) throws Exception {
        if (lg.getLayers().isEmpty()) {
            return;
        }

        LayerInfo l = lg.getLayers().get(0);
        ReferencedEnvelope bounds = l.getResource().boundingBox();
        boolean latlon = false;
        if (bounds == null) {
            bounds = l.getResource().getLatLonBoundingBox();
            latlon = true;
        }

        if (bounds == null) {
            throw new IllegalArgumentException(
                    "Could not calculate bounds from layer with no bounds, " + l.getName());
        }

        for (int i = 1; i < lg.getLayers().size(); i++) {
            l = lg.getLayers().get(i);

            ReferencedEnvelope re;
            if (latlon) {
                re = l.getResource().getLatLonBoundingBox();
            } else {
                re = l.getResource().boundingBox();
            }

            re = transform(re, bounds.getCoordinateReferenceSystem());
            if (re == null) {
                throw new IllegalArgumentException(
                        "Could not calculate bounds from layer with no bounds, " + l.getName());
            }
            bounds.expandToInclude(re);
        }

        lg.setBounds(bounds);
    }

    /**
     * Helper method for transforming an envelope.
     */
    ReferencedEnvelope transform(ReferencedEnvelope e, CoordinateReferenceSystem crs)
            throws TransformException, FactoryException {
        if (!CRS.equalsIgnoreMetadata(crs, e.getCoordinateReferenceSystem())) {
            return e.transform(crs, true);
        }
        return e;
    }

    //
    // remove methods
    //

    /**
     * Removes a workspace from the catalog.
     * <p>
     * The <tt>recursive</tt> flag controls whether objects linked to the workspace such as stores
     * should also be deleted.
     * </p>
     */
    public void removeWorkspace(WorkspaceInfo workspace, boolean recursive) {
        if (recursive) {
            workspace.accept(new CascadeDeleteVisitor(catalog));
        } else {
            catalog.remove(workspace);
        }
    }

    /**
     * Removes a store from the catalog.
     * <p>
     * The <tt>recursive</tt> flag controls whether objects linked to the store such as resources
     * should also be deleted.
     * </p>
     */
    public void removeStore(StoreInfo store, boolean recursive) {
        if (recursive) {
            store.accept(new CascadeDeleteVisitor(catalog));
        } else {
            catalog.remove(store);
        }
    }

    /**
     * Removes a resource from the catalog.
     * <p>
     * The <tt>recursive</tt> flag controls whether objects linked to the resource such as layers
     * should also be deleted.
     * </p>
     */
    public void removeResource(ResourceInfo resource, boolean recursive) {
        if (recursive) {
            resource.accept(new CascadeDeleteVisitor(catalog));
        } else {
            catalog.remove(resource);
        }
    }

    /**
     * Reattaches a serialized {@link StoreInfo} to the catalog
     */
    public void attach(StoreInfo storeInfo) {
        storeInfo = ModificationProxy.unwrap(storeInfo);
        ((StoreInfoImpl) storeInfo).setCatalog(catalog);
    }

    /**
     * Reattaches a serialized {@link ResourceInfo} to the catalog
     */
    public void attach(ResourceInfo resourceInfo) {
        resourceInfo = ModificationProxy.unwrap(resourceInfo);
        ((ResourceInfoImpl) resourceInfo).setCatalog(catalog);
    }

    /**
     * Reattaches a serialized {@link LayerInfo} to the catalog
     */
    public void attach(LayerInfo layerInfo) {
        attach(layerInfo.getResource());
    }

    /**
     * Reattaches a serialized {@link MapInfo} to the catalog
     */
    public void attach(MapInfo mapInfo) {
        // hmmm... mapInfo has a list of layers inside? Not names?
        for (LayerInfo layer : mapInfo.getLayers()) {
            attach(layer);
        }
    }

    /**
     * Reattaches a serialized {@link LayerGroupInfo} to the catalog
     */
    public void attach(LayerGroupInfo groupInfo) {
        for (LayerInfo layer : groupInfo.getLayers()) {
            attach(layer);
        }
        for (StyleInfo style : groupInfo.getStyles()) {
            if (style != null)
                attach(style);
        }
    }

    /**
     * Reattaches a serialized {@link StyleInfo} to the catalog
     */
    public void attach(StyleInfo styleInfo) {
        styleInfo = ModificationProxy.unwrap(styleInfo);
        ((StyleInfoImpl) styleInfo).setCatalog(catalog);
    }

    /**
     * Reattaches a serialized {@link NamespaceInfo} to the catalog
     */
    public void attach(NamespaceInfo nsInfo) {
        // nothing to do
    }

    /**
     * Reattaches a serialized {@link WorkspaceInfo} to the catalog
     */
    public void attach(WorkspaceInfo wsInfo) {
        // nothing to do
    }
}
TOP

Related Classes of org.geoserver.catalog.CatalogBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.