Package org.geoserver.wfs

Source Code of org.geoserver.wfs.GetFeature

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

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import net.opengis.wfs.AllSomeType;
import net.opengis.wfs.FeatureCollectionType;
import net.opengis.wfs.GetFeatureType;
import net.opengis.wfs.GetFeatureWithLockType;
import net.opengis.wfs.LockFeatureResponseType;
import net.opengis.wfs.LockFeatureType;
import net.opengis.wfs.LockType;
import net.opengis.wfs.QueryType;
import net.opengis.wfs.WfsFactory;
import net.opengis.wfs.XlinkPropertyNameType;

import org.geoserver.catalog.AttributeTypeInfo;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.config.GeoServer;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.SchemaException;
import org.geotools.filter.expression.AbstractExpressionVisitor;
import org.geotools.filter.visitor.AbstractFilterVisitor;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.LiteCoordinateSequenceFactory;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.xml.EMFUtils;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.ExpressionVisitor;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

/**
* Web Feature Service GetFeature operation.
* <p>
* This operation returns an array of {@link org.geotools.feature.FeatureCollection}
* instances.
* </p>
*
* @author Rob Hranac, TOPP
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
* @version $Id: GetFeature.java 13762 2009-12-09 02:22:13Z jdeolive $
*/
public class GetFeature {
    /** Standard logging instance for class */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.requests");

    /** The catalog */
    protected Catalog catalog;

    /** The wfs configuration */
    protected WFSInfo wfs;

    /** filter factory */
    protected FilterFactory filterFactory;

    /**
     * Creates the GetFeature operation.
     *
     */
    public GetFeature(WFSInfo wfs, Catalog catalog) {
        this.wfs = wfs;
        this.catalog = catalog;
    }

    /**
     * @return The reference to the GeoServer catalog.
     */
    public Catalog getCatalog() {
        return catalog;
    }

    /**
     * @return The reference to the WFS configuration.
     */
    public WFSInfo getWFS() {
        return wfs;
    }

    /**
     * Sets the filter factory to use to create filters.
     *
     * @param filterFactory
     */
    public void setFilterFactory(FilterFactory filterFactory) {
        this.filterFactory = filterFactory;
    }

    public FeatureCollectionType run(GetFeatureType request)
        throws WFSException {
        List queries = request.getQuery();

        if (queries.isEmpty()) {
            throw new WFSException("No query specified");
        }

        if (EMFUtils.isUnset(queries, "typeName")) {
            String msg = "No feature types specified";
            throw new WFSException(msg);
        }

        // Optimization Idea
        //
        // We should be able to reduce this to a two pass opperations.
        //
        // Pass #1 execute
        // - Attempt to Locks Fids during the first pass
        // - Also collect Bounds information during the first pass
        //
        // Pass #2 writeTo
        // - Using the Bounds to describe our FeatureCollections
        // - Iterate through FeatureResults producing GML
        //
        // And allways remember to release locks if we are failing:
        // - if we fail to aquire all the locks we will need to fail and
        //   itterate through the the FeatureSources to release the locks
        //
        if (request.getMaxFeatures() == null) {
            request.setMaxFeatures(BigInteger.valueOf(Integer.MAX_VALUE));
        }

        // take into consideration the wfs max features
        int maxFeatures = Math.min(request.getMaxFeatures().intValue(),
                wfs.getMaxFeatures());

        int count = 0; //should probably be long

        List results = new ArrayList();
        try {
            for (int i = 0; (i < request.getQuery().size()) && (count < maxFeatures); i++) {
                QueryType query = (QueryType) request.getQuery().get(i);

                FeatureTypeInfo meta = null;

                if (query.getTypeName().size() == 1) {
                    meta = featureTypeInfo((QName) query.getTypeName().get(0));
                } else {
                    //TODO: a join is taking place
                }

                FeatureSource<? extends FeatureType, ? extends Feature> source = meta.getFeatureSource(null,null);

                List<AttributeTypeInfo> atts = meta.attributes();
                List attNames = new ArrayList( atts.size() );
                for ( AttributeTypeInfo att : atts ) {
                    attNames.add( att.getName() );
                }

                //make sure property names are cool
                List propNames = query.getPropertyName();

                for (Iterator iter = propNames.iterator(); iter.hasNext();) {
                    String propName = (String) iter.next();

                    //HACK: strip off namespace
                    if (propName.indexOf(':') != -1) {
                        propName = propName.substring(propName.indexOf(':') + 1);
                    }

                    if (!attNames.contains(propName)) {
                        String mesg = "Requested property: " + propName + " is " + "not available "
                            + "for " + query.getTypeName() + ".  " + "The possible propertyName "
                            + "values are: " + attNames;

                        throw new WFSException(mesg);
                    }
                }

                //we must also include any properties that are mandatory ( even if not requested ),
                // ie. those with minOccurs > 0
                List extraGeometries = new ArrayList();
                List properties = new ArrayList();
                if (propNames.size() != 0) {
                    Iterator ii = atts.iterator();

                    while (ii.hasNext()) {
                        AttributeTypeInfo ati = (AttributeTypeInfo) ii.next();
                        LOGGER.finer("checking to see if " + propNames + " contains" + ati);

                        if (((ati.getMinOccurs() > 0) && (ati.getMaxOccurs() != 0))) {
                            //mandatory, add it
                            properties.add(ati.getName());

                            continue;
                        }

                        //check if it was requested
                        for (Iterator p = propNames.iterator(); p.hasNext();) {
                            String propName = (String) p.next();

                            if (propName.matches("(\\w+:)?" + ati.getName())) {
                                properties.add(ati.getName());
                                break;
                            }
                        }
                       
                        // if we need to force feature bounds computation, we have to load
                        // all of the geometries, but we'll have to remove them in the
                        // returned feature type
                        if(wfs.isFeatureBounding() && meta.getFeatureType().getDescriptor(ati.getName()) instanceof GeometryDescriptor
                                && !properties.contains(ati.getName())) {
                            properties.add(ati.getName());
                            extraGeometries.add(ati.getName());
                        }
                    }

                    //replace property names
                    query.getPropertyName().clear();
                    query.getPropertyName().addAll(properties);
                }

                //make sure filters are sane
                //
                // Validation of filters on non-simple feature types is not yet supported.
                // FIXME: Support validation of filters on non-simple feature types:
                // need to consider xpath properties and how to configure namespace prefixes in
                // GeoTools app-schema FeaturePropertyAccessorFactory.
                if (query.getFilter() != null && source.getSchema() instanceof SimpleFeatureType) {
                   
                    //1. ensure any property name refers to a property that
                    // actually exists
                    final FeatureType featureType = source.getSchema();
                    ExpressionVisitor visitor = new AbstractExpressionVisitor() {
                            public Object visit(PropertyName name, Object data) {
                                // case of multiple geometries being returned
                                if (name.evaluate(featureType) == null) {
                                    throw new WFSException("Illegal property name: "
                                        + name.getPropertyName(), "InvalidParameterValue");
                                }

                                return name;
                            }
                            ;
                        };
                    query.getFilter().accept(new AbstractFilterVisitor(visitor), null);
                   
                    //2. ensure any spatial predicate is made against a property
                    // that is actually special
                    AbstractFilterVisitor fvisitor = new AbstractFilterVisitor() {
                     
                        protected Object visit( BinarySpatialOperator filter, Object data ) {
                            PropertyName name = null;
                            if ( filter.getExpression1() instanceof PropertyName ) {
                                name = (PropertyName) filter.getExpression1();
                            }
                            else if ( filter.getExpression2() instanceof PropertyName ) {
                                name = (PropertyName) filter.getExpression2();
                            }
                           
                            if ( name != null ) {
                                //check against fetaure type to make sure its
                                // a geometric type
                                AttributeDescriptor att = (AttributeDescriptor) name.evaluate(featureType);
                                if ( !( att instanceof GeometryDescriptor ) ) {
                                    throw new WFSException("Property " + name + " is not geometric", "InvalidParameterValue");
                                }
                            }
                           
                            return filter;
                        }
                    };
                    query.getFilter().accept(fvisitor, null);
                   
                    //3. ensure that any bounds specified as part of the query
                    // are valid with respect to the srs defined on the query
                    if ( wfs.isCiteCompliant() ) {
                       
                        if ( query.getSrsName() != null ) {
                            final QueryType fquery = query;
                            fvisitor = new AbstractFilterVisitor() {
                                public Object visit(BBOX filter, Object data) {
                                    if ( filter.getSRS() != null &&
                                            !fquery.getSrsName().toString().equals( filter.getSRS() ) ) {
                                       
                                        //back project bounding box into geographic coordinates
                                        CoordinateReferenceSystem geo = DefaultGeographicCRS.WGS84;
                                       
                                        GeneralEnvelope e = new GeneralEnvelope(
                                            new double[] { filter.getMinX(), filter.getMinY()},
                                            new double[] { filter.getMaxX(), filter.getMaxY()}
                                        );
                                        CoordinateReferenceSystem crs = null;
                                        try {
                                            crs = CRS.decode( filter.getSRS() );
                                            e = CRS.transform(CRS.findMathTransform(crs, geo, true), e);
                                        }
                                        catch( Exception ex ) {
                                            throw new WFSException( ex );
                                        }
                                       
                                        //ensure within bounds defined by srs specified on
                                        // query
                                        try {
                                            crs = CRS.decode( fquery.getSrsName().toString() );
                                        }
                                        catch( Exception ex ) {
                                            throw new WFSException( ex );
                                        }
                                       
                                        GeographicBoundingBox valid =
                                            (GeographicBoundingBox) crs.getDomainOfValidity()
                                            .getGeographicElements().iterator().next();
                                       
                                        if ( e.getMinimum(0) < valid.getWestBoundLongitude() ||
                                            e.getMinimum(0) > valid.getEastBoundLongitude() ||
                                            e.getMaximum(0) < valid.getWestBoundLongitude() ||
                                            e.getMaximum(0) > valid.getEastBoundLongitude() ||
                                            e.getMinimum(1) < valid.getSouthBoundLatitude() ||
                                            e.getMinimum(1) > valid.getNorthBoundLatitude() ||
                                            e.getMaximum(1) < valid.getSouthBoundLatitude() ||
                                            e.getMaximum(1) > valid.getNorthBoundLatitude() ) {
                                               
                                            throw new WFSException( "bounding box out of valid range of crs", "InvalidParameterValue");
                                        }
                                    }
                                   
                                    return data;
                                }
                            };
                           
                            query.getFilter().accept(fvisitor, null);
                        }
                    }  
                }

                // handle local maximum
                int queryMaxFeatures = maxFeatures - count;
                if(meta.getMaxFeatures() > 0 && meta.getMaxFeatures() < queryMaxFeatures)
                    queryMaxFeatures = meta.getMaxFeatures();
                org.geotools.data.Query gtQuery = toDataQuery(query, queryMaxFeatures, source, request);
               
                LOGGER.fine("Query is " + query + "\n To gt2: " + gtQuery);

                FeatureCollection<? extends FeatureType, ? extends Feature> features = getFeatures(request, source, gtQuery);
               
                // optimization: WFS 1.0 does not require count unless we have multiple query elements
                // and we are asked to perform a global limit on the results returned
                if(("1.0".equals(request.getVersion()) || "1.0.0".equals(request.getVersion())) &&
                        (request.getQuery().size() == 1 || maxFeatures == Integer.MAX_VALUE)) {
                    // skip the count update, in this case we don't need it
                } else {
                  count += features.size();
                }
               
                // we may need to shave off geometries we did load only to make bounds
                // computation happy
                // TODO: support non-SimpleFeature geometry shaving
                if(features.getSchema() instanceof SimpleFeatureType && extraGeometries.size() > 0) {
                    List residualProperties = new ArrayList(properties);
                    residualProperties.removeAll(extraGeometries);
                    String[] residualNames = (String[]) residualProperties.toArray(new String[residualProperties.size()]);
                    SimpleFeatureType targetType = DataUtilities.createSubType((SimpleFeatureType) features.getSchema(), residualNames);
                    features = new FeatureBoundsFeatureCollection((FeatureCollection<SimpleFeatureType, SimpleFeature>) features, targetType);
                }

                //JD: TODO reoptimize
                //                if ( i == request.getQuery().size() - 1 ) {
                //                  //DJB: dont calculate feature count if you dont have to. The MaxFeatureReader will take care of the last iteration
                //                  maxFeatures -= features.getCount();
                //                }

                //GR: I don't know if the featuresults should be added here for later
                //encoding if it was a lock request. may be after ensuring the lock
                //succeed?
                results.add(features);
            }
        } catch (IOException e) {
            throw new WFSException("Error occurred getting features", e, request.getHandle());
        } catch (SchemaException e) {
            throw new WFSException("Error occurred getting features", e, request.getHandle());
        }

        //locking
        String lockId = null;
        if (request instanceof GetFeatureWithLockType) {
            GetFeatureWithLockType withLockRequest = (GetFeatureWithLockType) request;

            LockFeatureType lockRequest = WfsFactory.eINSTANCE.createLockFeatureType();
            lockRequest.setExpiry(withLockRequest.getExpiry());
            lockRequest.setHandle(withLockRequest.getHandle());
            lockRequest.setLockAction(AllSomeType.ALL_LITERAL);

            for (int i = 0; i < request.getQuery().size(); i++) {
                QueryType query = (QueryType) request.getQuery().get(i);

                LockType lock = WfsFactory.eINSTANCE.createLockType();
                lock.setFilter(query.getFilter());
                lock.setHandle(query.getHandle());

                //TODO: joins?
                lock.setTypeName((QName) query.getTypeName().get(0));
                lockRequest.getLock().add(lock);
            }

            LockFeature lockFeature = new LockFeature(wfs, catalog);
            lockFeature.setFilterFactory(filterFactory);

            LockFeatureResponseType response = lockFeature.lockFeature(lockRequest);
            lockId = response.getLockId();
        }

        return buildResults(count, results, lockId);
    }

    /**
     * Allows subclasses to alter the result generation
     * @param count
     * @param results
     * @param lockId
     * @return
     */
    protected FeatureCollectionType buildResults(int count, List results,
            String lockId) {
        FeatureCollectionType result = WfsFactory.eINSTANCE.createFeatureCollectionType();
        result.setNumberOfFeatures(BigInteger.valueOf(count));
        result.setTimeStamp(Calendar.getInstance());
        result.setLockId(lockId);
        result.getFeature().addAll(results);
        return result;
    }

    /**
     * Allows subclasses to poke with the feature collection extraction
     * @param source
     * @param gtQuery
     * @return
     * @throws IOException
     */
    protected FeatureCollection<? extends FeatureType, ? extends Feature> getFeatures(
            GetFeatureType request, FeatureSource<? extends FeatureType, ? extends Feature> source,
            org.geotools.data.Query gtQuery)
            throws IOException {
        return source.getFeatures(gtQuery);
    }

    /**
     * Get this query as a geotools Query.
     *
     * <p>
     * if maxFeatures is a not positive value DefaultQuery.DEFAULT_MAX will be
     * used.
     * </p>
     *
     * <p>
     * The method name is changed to toDataQuery since this is a one way
     * conversion.
     * </p>
     *
     * @param maxFeatures number of features, or 0 for DefaultQuery.DEFAULT_MAX
     *
     * @return A Query for use with the FeatureSource interface
     *
     */
    public org.geotools.data.Query toDataQuery(QueryType query, int maxFeatures,
        FeatureSource<? extends FeatureType, ? extends Feature> source, GetFeatureType request) throws WFSException {
       
        String wfsVersion = request.getVersion();
       
        if (maxFeatures <= 0) {
            maxFeatures = DefaultQuery.DEFAULT_MAX;
        }

        String[] props = null;

        if (!query.getPropertyName().isEmpty()) {
            props = new String[query.getPropertyName().size()];

            for (int p = 0; p < query.getPropertyName().size(); p++) {
                String propertyName = (String) query.getPropertyName().get(p);
                props[p] = propertyName;
            }
        }

        Filter filter = (Filter) query.getFilter();

        if (filter == null) {
            filter = Filter.INCLUDE;
        }
       
        //figure out the crs the data is in
        CoordinateReferenceSystem crs = source.getSchema().getCoordinateReferenceSystem();
           
        // gather declared CRS
        CoordinateReferenceSystem declaredCRS = WFSReprojectionUtil.getDeclaredCrs(crs, wfsVersion);
       
        // make sure every bbox and geometry that does not have an attached crs will use
        // the declared crs, and then reproject it to the native crs
        Filter transformedFilter = filter;
        if(declaredCRS != null)
            transformedFilter = WFSReprojectionUtil.normalizeFilterCRS(filter, source.getSchema(), declaredCRS);

        //only handle non-joins for now
        QName typeName = (QName) query.getTypeName().get(0);
        DefaultQuery dataQuery = new DefaultQuery(typeName.getLocalPart(), transformedFilter, maxFeatures,
                props, query.getHandle());
       
        //handle reprojection
        CoordinateReferenceSystem target;
        if (query.getSrsName() != null) {
            try {
                target = CRS.decode(query.getSrsName().toString());
            } catch (Exception e) {
                String msg = "Unable to support srsName: " + query.getSrsName();
                throw new WFSException(msg, e);
            }
        } else {
            target = declaredCRS;
        }
        //if the crs are not equal, then reproject
        if (target != null && declaredCRS != null && !CRS.equalsIgnoreMetadata(crs, target)) {
            dataQuery.setCoordinateSystemReproject(target);
        }
       
        //handle sorting
        if (query.getSortBy() != null) {
            List sortBy = query.getSortBy();
            dataQuery.setSortBy((SortBy[]) sortBy.toArray(new SortBy[sortBy.size()]));
        }

        //handle version, datastore may be able to use it
        if (query.getFeatureVersion() != null) {
            dataQuery.setVersion(query.getFeatureVersion());
        }

        //create the Hints to set at the end
        final Hints hints = new Hints();
               
        //handle xlink traversal depth
        if (request.getTraverseXlinkDepth() != null) {
            //TODO: make this an integer in the model, and have hte NumericKvpParser
            // handle '*' as max value
            Integer traverseXlinkDepth = traverseXlinkDepth( request.getTraverseXlinkDepth() );
           
            //set the depth as a hint on the query
            hints.put(Hints.ASSOCIATION_TRAVERSAL_DEPTH, traverseXlinkDepth);
        }
       
        //handle xlink properties
        if (!query.getXlinkPropertyName().isEmpty() ) {
            for ( Iterator x = query.getXlinkPropertyName().iterator(); x.hasNext(); ) {
                XlinkPropertyNameType xlinkProperty = (XlinkPropertyNameType) x.next();
               
                Integer traverseXlinkDepth = traverseXlinkDepth( xlinkProperty.getTraverseXlinkDepth() );
               
                //set the depth and property as hints on the query
                hints.put(Hints.ASSOCIATION_TRAVERSAL_DEPTH, traverseXlinkDepth );
               
                PropertyName xlinkPropertyName = filterFactory.property( xlinkProperty.getValue() );
                hints.put(Hints.ASSOCIATION_PROPERTY, xlinkPropertyName );
               
                dataQuery.setHints( hints );
               
                //TODO: support multiple properties
                break;
            }
        }
       
        //tell the datastore to use a lite coordinate sequence factory, if possible
        hints.put(Hints.JTS_COORDINATE_SEQUENCE_FACTORY, new LiteCoordinateSequenceFactory());

        //finally, set the hints
        dataQuery.setHints(hints);
       
        return dataQuery;
    }

    static Integer traverseXlinkDepth( String raw ) {
        Integer traverseXlinkDepth = null;
        try {
            traverseXlinkDepth = new Integer( raw );
        }
        catch( NumberFormatException nfe ) {
            //try handling *
            if ( "*".equals( raw ) ) {
                //TODO: JD: not sure what this value should be? i think it
                // might be reported in teh acapabilitis document, using
                // INteger.MAX_VALUE will result in stack overflow... for now
                // we just use 10
                traverseXlinkDepth = new Integer( 2 );
            }
            else {
                //not wildcard case, throw original exception
                throw nfe;
            }
        }
       
        return traverseXlinkDepth;
    }
   
    FeatureTypeInfo featureTypeInfo(QName name) throws WFSException, IOException {
        FeatureTypeInfo meta = catalog.getFeatureTypeByName(name.getNamespaceURI(), name.getLocalPart());

        if (meta == null) {
            String msg = "Could not locate " + name + " in catalog.";
            throw new WFSException(msg);
        }

        return meta;
    }
}
TOP

Related Classes of org.geoserver.wfs.GetFeature

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.