org.polymap.core.data.PipelineFeatureSource.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.core.data.PipelineFeatureSource.java

Source

/*
 * polymap.org
 * Copyright 2009, 2011 Polymap GmbH. All rights reserved.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 */
package org.polymap.core.data;

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

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.filter.Filter;
import org.opengis.filter.identity.FeatureId;
import org.opengis.util.ProgressListener;

import org.geotools.data.AbstractFeatureSource;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.NullProgressListener;
import org.geotools.util.SimpleInternationalString;

import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.catalog.IService;

import org.polymap.core.data.feature.AddFeaturesRequest;
import org.polymap.core.data.feature.GetFeatureTypeRequest;
import org.polymap.core.data.feature.GetFeatureTypeResponse;
import org.polymap.core.data.feature.GetFeaturesRequest;
import org.polymap.core.data.feature.GetFeaturesResponse;
import org.polymap.core.data.feature.GetFeaturesSizeRequest;
import org.polymap.core.data.feature.GetFeaturesSizeResponse;
import org.polymap.core.data.feature.ModifyFeaturesRequest;
import org.polymap.core.data.feature.ModifyFeaturesResponse;
import org.polymap.core.data.feature.RemoveFeaturesRequest;
import org.polymap.core.data.pipeline.DefaultPipelineIncubator;
import org.polymap.core.data.pipeline.Pipeline;
import org.polymap.core.data.pipeline.PipelineIncubationException;
import org.polymap.core.data.pipeline.ProcessorResponse;
import org.polymap.core.data.pipeline.ResponseHandler;
import org.polymap.core.model.security.ACLUtils;
import org.polymap.core.model.security.AclPermission;
import org.polymap.core.project.ILayer;
import org.polymap.core.project.LayerUseCase;
import org.polymap.core.runtime.SessionContext;

/**
 * This <code>FeatureSource</code> provides the features of an {@link ILayer}
 * (its underlaying {@link IService}), processed by the layer specific
 * {@link Pipeline}, instantiated for use-case {@link LayerUseCase#FEATURES}.
 *
 * @author <a href="http://www.polymap.de">Falko Braeutigam</a>
 * @since 3.0
 */
public class PipelineFeatureSource extends AbstractFeatureSource
        implements FeatureStore<SimpleFeatureType, SimpleFeature> {

    private static final Log log = LogFactory.getLog(PipelineFeatureSource.class);

    private static DefaultPipelineIncubator pipelineIncubator = new DefaultPipelineIncubator();

    // static factory *************************************

    /**
     * Instantiates a new pipelined {@link FeatureSource} for the given layer.
     * <p>
     * This method may block execution while accessing the back-end service.
     * 
     * @return The newly created <code>FeatureSource</code>, or null if no
     *         {@link FeatureSource} could be created for this layer because its is a
     *         raster layer or no appropriate processors could be found.
     * @throws PipelineIncubationException
     * @throws IOException
     * @throws IllegalStateException If the geo resource for the given layer could
     *         not be find.
     */
    public static PipelineFeatureSource forLayer(ILayer layer, boolean transactional)
            throws PipelineIncubationException, IOException {
        assert layer != null : "layer == null is not allowed";
        log.debug("layer: " + layer + ", label= " + layer.getLabel() + ", visible= " + layer.isVisible());

        IGeoResource res = layer.getGeoResource();
        if (res == null) {
            throw new IllegalStateException("Unable to find geo resource of layer: " + layer);
        }
        IService service = res.service(null);
        log.debug("service: " + service);

        // create pipeline
        LayerUseCase useCase = transactional ? LayerUseCase.FEATURES_TRANSACTIONAL : LayerUseCase.FEATURES;
        Pipeline pipe = pipelineIncubator.newPipeline(useCase, layer.getMap(), layer, service);

        return pipe.length() > 0
                // create FeatureSource
                ? new PipelineFeatureSource(pipe)
                : null;
    }

    // instance *******************************************

    private Pipeline pipeline;

    private PipelineDataStore store;

    private Transaction tx = Transaction.AUTO_COMMIT;

    private SessionContext sessionContext = SessionContext.current();

    public PipelineFeatureSource(Pipeline pipeline) {
        super();
        this.pipeline = pipeline;
        // FIXME this has to be unique instance per session
        this.store = new PipelineDataStore(this);
    }

    public DataStore getDataStore() {
        return store;
    }

    public Pipeline getPipeline() {
        return pipeline;
    }

    public ILayer getLayer() {
        Set<ILayer> layers = getPipeline().getLayers();
        assert layers.size() == 1;
        return layers.iterator().next();
    }

    public ReferencedEnvelope getBounds(Query query) throws IOException {
        // XXX optimize getBounds via dedicated request
        log.info("XXX: getBounds: iterating over collection!");
        ReferencedEnvelope result = new ReferencedEnvelope();
        FeatureIterator<SimpleFeature> it = getFeatures(query).features();
        try {
            while (it.hasNext()) {
                SimpleFeature feature = it.next();
                result.include(feature.getBounds());
            }
        } finally {
            it.close();
        }
        return result;
    }

    public int getCount(Query query) throws IOException {
        return getFeaturesSize(query);
    }

    public SimpleFeatureType getSchema() {
        // XXX caching schema is not allowed since the pipeline and/or processors
        // may change; maybe we could add a listener API to the pipeline

        // avoid synchronize; doing this in parallel is ok
        GetFeatureTypeRequest request = new GetFeatureTypeRequest();
        try {
            final FeatureType[] type = new FeatureType[1];
            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    GetFeatureTypeResponse response = (GetFeatureTypeResponse) r;
                    type[0] = response.getFeatureType();
                }
            });
            return (SimpleFeatureType) type[0];
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatures(Query query) throws IOException {
        log.debug("query= " + query);
        //assert query.getFilter() != null : "No filter in query.";
        if (query.getFilter() == null) {
            log.warn(
                    "Filter is NULL -> changing to EXCLUDE to prevent unwanted loading of all features! Use INCLUDE to get all.");
            query = new DefaultQuery(query);
            ((DefaultQuery) query).setFilter(Filter.EXCLUDE);
        }
        return new AsyncPipelineFeatureCollection(this, query, sessionContext);
    }

    /**
     * Called by {@link SyncPipelineFeatureCollection} to fetch feature chunks.
     */
    protected void fetchFeatures(Query query, final FeatureResponseHandler handler) throws Exception {
        try {
            log.debug("fetchFeatures(): maxFeatures= " + query.getMaxFeatures());
            GetFeaturesRequest request = new GetFeaturesRequest(query);

            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    GetFeaturesResponse response = (GetFeaturesResponse) r;
                    handler.handle(response.getFeatures());
                }
            });
        }
        //        catch (RuntimeException e) {
        //            throw e;
        //        }
        //        catch (Exception e) {
        //            throw new RuntimeException( e );
        //        }
        finally {
            handler.endOfResponse();
        }
    }

    protected interface FeatureResponseHandler {

        public void handle(List<Feature> features) throws Exception;

        public void endOfResponse() throws Exception;
    }

    /**
     * Called by {@link SyncPipelineFeatureCollection} to determine the size
     * of the result collection of the given query.
     */
    protected int getFeaturesSize(Query query) {
        GetFeaturesSizeRequest request = new GetFeaturesSizeRequest(query);
        try {
            final int[] result = new int[1];
            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    GetFeaturesSizeResponse response = (GetFeaturesSizeResponse) r;
                    result[0] = response.getSize();
                }
            });
            return result[0];
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void addFeatureListener(FeatureListener l) {
        store.listeners.addFeatureListener(this, l);
    }

    public void removeFeatureListener(FeatureListener l) {
        store.listeners.removeFeatureListener(this, l);
    }

    // FeatureStore ***************************************

    protected void checkWritePermission() {
        try {
            ACLUtils.checkPermission(getLayer(), AclPermission.WRITE, true);
        } catch (SecurityException e) {
            throw e;
        } catch (Throwable e) {
            log.error("Error while checking WRITE permission for layer.", e);
        }
    }

    public void setTransaction(Transaction transaction) {
        log.warn(
                "PipelinedFeatureSource: no transaction support as updates are bufferd by LayerFeatureBufferManager!");
    }

    public List<FeatureId> addFeatures(FeatureCollection<SimpleFeatureType, SimpleFeature> features)
            throws IOException {
        return addFeatures(features, new NullProgressListener());
    }

    public List<FeatureId> addFeatures(FeatureCollection<SimpleFeatureType, SimpleFeature> features,
            final ProgressListener monitor) throws IOException {
        checkWritePermission();

        monitor.started();

        //        FeatureType schema = getSchema();
        //        if (!schema.equals( features.getSchema() )) {
        //            log.warn( "addFeatures(): Given features have different schema - performing retype..." );
        //            features = new ReTypingFeatureCollection( features, (SimpleFeatureType)schema );
        //        }
        final FeatureCollection fc = features;
        // build a Collection that pipes the features through its Iterator; so
        // the features don't need to be loaded in memory all together; and no
        // chunks are needed; and events are sent correctly
        Collection coll = new AbstractCollection() {
            private volatile int size = -1;

            public int size() {
                return size < 0 ? size = fc.size() : size;
            }

            public Iterator iterator() {
                return new Iterator() {
                    private FeatureIterator it = fc.features();
                    private int count = 0;

                    @Override
                    public boolean hasNext() {
                        if (it != null && !it.hasNext()) {
                            it.close();
                            it = null;
                            return false;
                        } else {
                            return true;
                        }
                    }

                    @Override
                    public Object next() {
                        if ((++count % 100) == 0) {
                            monitor.setTask(new SimpleInternationalString("" + count));
                            monitor.progress(100);
                        }
                        return it.next();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    protected void finalize() throws Throwable {
                        if (it != null) {
                            it.close();
                            it = null;
                        }
                    }
                };
            }
        };

        try {
            final List<FeatureId> fids = new ArrayList(1024);
            // request
            AddFeaturesRequest request = new AddFeaturesRequest(features.getSchema(), coll);
            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    fids.addAll(((ModifyFeaturesResponse) r).getFeatureIds());
                }
            });

            // fire event
            store.listeners.fireFeaturesAdded(getSchema().getTypeName(), tx, null, false);

            monitor.complete();
            return fids;
        } catch (RuntimeException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void removeFeatures(Filter filter) throws IOException {
        checkWritePermission();
        try {
            // request
            RemoveFeaturesRequest request = new RemoveFeaturesRequest(filter);
            final ModifyFeaturesResponse[] response = new ModifyFeaturesResponse[1];
            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    response[0] = (ModifyFeaturesResponse) r;
                }
            });
            // fire event
            store.listeners.fireFeaturesRemoved(getSchema().getTypeName(), tx, null, false);
            return;
        } catch (RuntimeException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void modifyFeatures(AttributeDescriptor type, Object value, Filter filter) throws IOException {
        modifyFeatures(new AttributeDescriptor[] { type }, new Object[] { value }, filter);
    }

    public void modifyFeatures(AttributeDescriptor[] type, Object[] value, Filter filter) throws IOException {
        checkWritePermission();
        try {
            // request
            ModifyFeaturesRequest request = new ModifyFeaturesRequest(type, value, filter);
            final ModifyFeaturesResponse[] response = new ModifyFeaturesResponse[1];
            pipeline.process(request, new ResponseHandler() {
                public void handle(ProcessorResponse r) throws Exception {
                    response[0] = (ModifyFeaturesResponse) r;
                }
            });
            // fire event
            log.debug("Event: type=" + getName().getLocalPart());
            store.listeners.fireFeaturesChanged(getName().getLocalPart(), tx, null, false);
            //            store.listeners.fireEvent( getName().getLocalPart(), tx,
            //                    new FeatureEvent( this, FeatureEvent.Type.CHANGED, null, filter ) );
            return;
        } catch (RuntimeException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    public void setFeatures(FeatureReader<SimpleFeatureType, SimpleFeature> reader) throws IOException {
        // XXX Auto-generated method stub
        throw new RuntimeException("not yet implemented.");
    }

}