org.deegree.feature.persistence.geocouch.GeoCouchFeatureStore.java Source code

Java tutorial

Introduction

Here is the source code for org.deegree.feature.persistence.geocouch.GeoCouchFeatureStore.java

Source

//$HeadURL: svn+ssh://aschmitz@deegree.wald.intevation.de/deegree/deegree3/trunk/deegree-datastores/deegree-featurestore/deegree-featurestore-shape/src/main/java/org/deegree/feature/persistence/shape/ShapeFeatureStore.java $
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2009 by:
 - Department of Geography, University of Bonn -
 and
 - lat/lon GmbH -
    
 This library 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 library 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.
 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 Contact information:
    
 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/
    
 Department of Geography, University of Bonn
 Prof. Dr. Klaus Greve
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/
    
 e-mail: info@deegree.org
 ----------------------------------------------------------------------------*/
package org.deegree.feature.persistence.geocouch;

import static org.apache.commons.io.IOUtils.readLines;
import static org.deegree.commons.utils.net.HttpUtils.STREAM;
import static org.deegree.feature.persistence.sql.blob.BlobCodec.Compression.NONE;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.ByteArrayInputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.deegree.commons.config.DeegreeWorkspace;
import org.deegree.commons.config.ResourceInitException;
import org.deegree.commons.tom.gml.GMLObject;
import org.deegree.commons.utils.CloseableIterator;
import org.deegree.commons.utils.net.HttpUtils;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.feature.Feature;
import org.deegree.feature.Features;
import org.deegree.feature.persistence.FeatureStore;
import org.deegree.feature.persistence.FeatureStoreException;
import org.deegree.feature.persistence.FeatureStoreTransaction;
import org.deegree.feature.persistence.lock.LockManager;
import org.deegree.feature.persistence.query.Query;
import org.deegree.feature.persistence.sql.blob.BlobCodec;
import org.deegree.feature.persistence.sql.blob.BlobCodec.Compression;
import org.deegree.feature.stream.CombinedFeatureInputStream;
import org.deegree.feature.stream.FeatureInputStream;
import org.deegree.feature.stream.FilteredFeatureInputStream;
import org.deegree.feature.stream.IteratorFeatureInputStream;
import org.deegree.feature.stream.MemoryFeatureInputStream;
import org.deegree.feature.types.AppSchema;
import org.deegree.feature.types.FeatureType;
import org.deegree.filter.Filter;
import org.deegree.filter.FilterEvaluationException;
import org.deegree.filter.IdFilter;
import org.deegree.filter.Operator;
import org.deegree.filter.OperatorFilter;
import org.deegree.filter.logical.And;
import org.deegree.filter.sort.SortProperty;
import org.deegree.filter.spatial.BBOX;
import org.deegree.geometry.Envelope;
import org.deegree.geometry.primitive.Point;
import org.deegree.protocol.wfs.getfeature.TypeName;
import org.deegree.workspace.Resource;
import org.deegree.workspace.ResourceMetadata;
import org.postgresql.util.Base64;
import org.slf4j.Logger;

import com.google.gson.JsonObject;

/**
 * {@link FeatureStore} implementation that uses GeoCouch as backend.
 * 
 * @see FeatureStore
 * 
 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
 * @author last edited by: $Author: mschneider $
 * 
 * @version $Revision: 30411 $, $Date: 2011-04-11 19:37:01 +0200 (Mon, 11 Apr 2011) $
 */
public class GeoCouchFeatureStore implements FeatureStore {

    private static final Logger LOG = getLogger(GeoCouchFeatureStore.class);

    private ICRS crs;

    private AppSchema schema;

    private String couchUrl;

    public GeoCouchFeatureStore(ICRS crs, AppSchema schema, String couchUrl) {
        this.crs = crs;
        this.schema = schema;
        this.couchUrl = couchUrl;
    }

    @Override
    public void init(DeegreeWorkspace workspace) throws ResourceInitException {
        try {
            JsonObject obj = HttpUtils.get(HttpUtils.JSON, couchUrl + "/couchBase/default", null).getAsJsonObject();
            if (obj.get("spatial") == null) {
                // set up index
                HttpClient client = new DefaultHttpClient();
                HttpPut put = new HttpPut(couchUrl + "/couchBase/default/_design/main");

                JsonObject spatial = new JsonObject();
                JsonObject indexes = new JsonObject();
                spatial.add("spatial", indexes);
                for (FeatureType type : schema.getFeatureTypes()) {
                    String name = type.getName().toString();
                    String indexFunc = "function(doc){" + "if(doc.bbox && doc.feature_type == '" + name + "'){"
                            + "emit({" + "type: \'Point\', bbox: doc.bbox, coordinates: [0, 0]" + "}, doc.blob);"
                            + "}};";
                    indexes.addProperty(name, indexFunc);
                }

                put.setEntity(new StringEntity(spatial.toString()));
                HttpResponse resp = client.execute(put);
                System.out.println(IOUtils.toString(resp.getEntity().getContent()));
                client.getConnectionManager().shutdown();
            }
        } catch (Throwable e) {
            throw new ResourceInitException("Error connecting to GeoCouch.", e);
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public AppSchema getSchema() {
        return schema;
    }

    @Override
    public boolean isMapped(QName ftName) {
        return schema.getFeatureType(ftName) != null;
    }

    @Override
    public Envelope getEnvelope(QName ftName) throws FeatureStoreException {
        // TODO use cache
        return null;
    }

    @Override
    public Envelope calcEnvelope(QName ftName) throws FeatureStoreException {
        return null;
    }

    @Override
    public FeatureInputStream query(Query query) throws FeatureStoreException, FilterEvaluationException {
        if (query.getTypeNames() == null || query.getTypeNames().length > 1) {
            String msg = "Join queries between multiple feature types are not supported yet.";
            throw new UnsupportedOperationException(msg);
        }

        FeatureInputStream result = null;
        Filter filter = query.getFilter();

        if (query.getTypeNames().length == 1 && (filter == null || filter instanceof OperatorFilter)) {
            QName ftName = query.getTypeNames()[0].getFeatureTypeName();
            FeatureType ft = getSchema().getFeatureType(ftName);
            if (ft == null) {
                String msg = "Feature type '" + ftName + "' is not served by this feature store.";
                throw new FeatureStoreException(msg);
            }
            result = queryByOperatorFilter(query, ftName, (OperatorFilter) filter);
        } else {
            // must be an id filter based query
            if (query.getFilter() == null || !(query.getFilter() instanceof IdFilter)) {
                String msg = "Invalid query. If no type names are specified, it must contain an IdFilter.";
                throw new FilterEvaluationException(msg);
            }
            result = queryByIdFilter((IdFilter) filter, query.getSortProperties());
        }
        return result;
    }

    private FeatureInputStream queryByIdFilter(IdFilter filter, SortProperty[] sortCrit)
            throws FeatureStoreException {

        FeatureInputStream result = null;
        BlobCodec codec = new BlobCodec(schema.getGMLSchema().getVersion(), Compression.NONE);
        //        try {
        //            result = new IteratorFeatureInputStream( new IDIterator( filter.getMatchingIds().iterator(), codec ) );
        //        } catch ( Throwable e ) {
        //            String msg = "Error performing id query: " + e.getMessage();
        //            LOG.debug( msg, e );
        //            throw new FeatureStoreException( msg, e );
        //        }

        // sort features
        if (sortCrit.length > 0) {
            result = new MemoryFeatureInputStream(Features.sortFc(result.toCollection(), sortCrit));
        }
        return result;
    }

    private FeatureInputStream queryByOperatorFilter(Query query, QName ftName, OperatorFilter filter)
            throws FeatureStoreException {
        LOG.debug("Performing blob query by operator filter");
        FeatureInputStream result = null;

        try {
            if (query.getPrefilterBBoxEnvelope() != null) {
                List<String> ids = new ArrayList<String>();
                List<String> blobs = new ArrayList<String>();
                Envelope box = query.getPrefilterBBoxEnvelope();
                Point min = box.getMin();
                Point max = box.getMax();

                Pattern p = Pattern.compile("\"id\"[:]\"([^\"]*)");
                Pattern blob = Pattern.compile("\"value\"[:]\"([^\"]*)");
                Matcher m = p.matcher("nix");
                Matcher blobm = blob.matcher("nix");

                for (TypeName name : query.getTypeNames()) {
                    String idxname = name.getFeatureTypeName().toString();
                    String url = couchUrl + "/couchBase/default/_design/main/_spatial/"
                            + URLEncoder.encode(idxname, "UTF-8") + "?bbox=";
                    url += min.get0() + "," + min.get1() + "," + max.get0() + "," + max.get1();
                    List<String> lines = readLines(HttpUtils.get(STREAM, url, null));
                    for (String line : lines) {
                        m.reset(line);
                        blobm.reset(line);
                        if (m.find()) {
                            ids.add(m.group(1));
                        }
                        if (blobm.find()) {
                            blobs.add(blobm.group(1));
                        }
                    }
                }

                BlobCodec codec = new BlobCodec(schema.getGMLSchema().getVersion(), Compression.NONE);
                result = new IteratorFeatureInputStream(new IDIterator(ids.iterator(), blobs.iterator(), codec));

                // mangle filter to exclude bbox
                if (filter != null && filter.getOperator() instanceof BBOX) {
                    filter = null;
                } else if (filter != null && filter.getOperator() instanceof And) {
                    And and = (And) filter.getOperator();
                    List<Operator> ops = new ArrayList<Operator>();
                    for (Operator o : and.getParams()) {
                        if (!(o instanceof BBOX))
                            ops.add(o);
                    }
                    if (ops.isEmpty()) {
                        filter = null;
                    }
                    if (ops.size() == 1) {
                        filter = new OperatorFilter(ops.get(0));
                    } else {
                        filter = new OperatorFilter(new And(ops.toArray(new Operator[ops.size()])));
                    }
                }
            } else {

            }
        } catch (Throwable e) {
            String msg = "Error performing query by operator filter: " + e.getMessage();
            LOG.error(msg, e);
            throw new FeatureStoreException(msg, e);
        }

        if (filter != null) {
            LOG.debug("Applying in-memory post-filtering.");
            result = new FilteredFeatureInputStream(result, filter);
        }

        if (query.getSortProperties().length > 0) {
            LOG.debug("Applying in-memory post-sorting.");
            result = new MemoryFeatureInputStream(
                    Features.sortFc(result.toCollection(), query.getSortProperties()));
        }
        return result;
    }

    @Override
    public FeatureInputStream query(final Query[] queries) throws FeatureStoreException, FilterEvaluationException {

        // check for most common case: multiple featuretypes, same bbox (WMS), no filter
        boolean wmsStyleQuery = false;
        Envelope env = queries[0].getPrefilterBBoxEnvelope();
        if (queries[0].getFilter() == null && queries[0].getSortProperties().length == 0) {
            wmsStyleQuery = true;
            for (int i = 1; i < queries.length; i++) {
                Envelope queryBBox = queries[i].getPrefilterBBoxEnvelope();
                if (queryBBox != env && queries[i].getFilter() != null && queries[i].getSortProperties() != null) {
                    wmsStyleQuery = false;
                    break;
                }
            }
        }

        if (wmsStyleQuery) {
            // return queryMultipleFts( queries, env );
        }

        Iterator<FeatureInputStream> rsIter = new Iterator<FeatureInputStream>() {
            int i = 0;

            @Override
            public boolean hasNext() {
                return i < queries.length;
            }

            @Override
            public FeatureInputStream next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                FeatureInputStream rs;
                try {
                    rs = query(queries[i++]);
                } catch (Throwable e) {
                    LOG.debug(e.getMessage(), e);
                    throw new RuntimeException(e.getMessage(), e);
                }
                return rs;
            }

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

    @Override
    public int queryHits(Query query) throws FeatureStoreException, FilterEvaluationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public int[] queryHits(Query[] queries) throws FeatureStoreException, FilterEvaluationException {
        throw new UnsupportedOperationException();
    }

    @Override
    public GMLObject getObjectById(String id) throws FeatureStoreException {
        throw new UnsupportedOperationException();
    }

    private Feature getFeatureById(String id) {
        BlobCodec codec = new BlobCodec(schema.getGMLSchema().getVersion(), NONE);
        HttpClient client = new DefaultHttpClient();
        try {
            HttpGet get = new HttpGet(couchUrl + id + "/feature");
            HttpResponse resp = client.execute(get);
            return (Feature) codec.decode(resp.getEntity().getContent(), schema.getNamespaceBindings(), schema, crs,
                    null);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            client.getConnectionManager().shutdown();
        }
        return null;
    }

    @Override
    public FeatureStoreTransaction acquireTransaction() throws FeatureStoreException {
        return new GeoCouchFeatureStoreTransaction(this, crs, couchUrl);
    }

    @Override
    public LockManager getLockManager() throws FeatureStoreException {
        // TODO Auto-generated method stub
        return null;
    }

    private class IDIterator implements CloseableIterator<Feature> {

        private final Iterator<String> ids;

        private final BlobCodec codec;

        private final Iterator<String> blobs;

        IDIterator(Iterator<String> ids, Iterator<String> blobs, BlobCodec codec) {
            this.ids = ids;
            this.blobs = blobs;
            this.codec = codec;
        }

        @Override
        public void close() {
            // nothing to do
        }

        @Override
        public Collection<Feature> getAsCollectionAndClose(Collection<Feature> collection) {
            while (hasNext()) {
                collection.add(next());
            }
            return collection;
        }

        @Override
        public List<Feature> getAsListAndClose() {
            return (List<Feature>) getAsCollectionAndClose(new LinkedList<Feature>());
        }

        @Override
        public boolean hasNext() {
            return blobs.hasNext();
        }

        @Override
        public Feature next() {
            byte[] bs = Base64.decode(blobs.next());
            try {
                return (Feature) codec.decode(new ByteArrayInputStream(bs), schema.getNamespaceBindings(), schema,
                        crs, null);
            } catch (Throwable e) {
                e.printStackTrace();
            }
            return null;
        }

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

    /* (non-Javadoc)
     * @see org.deegree.workspace.Resource#getMetadata()
     */
    @Override
    public ResourceMetadata<? extends Resource> getMetadata() {
        // TODO Auto-generated method stub
        return null;
    }

    /* (non-Javadoc)
     * @see org.deegree.workspace.Resource#init()
     */
    @Override
    public void init() {
        // TODO Auto-generated method stub

    }

}