/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2015, Geomatys * * 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; * version 2.1 of the License. * * 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. */ package org.geotoolkit.data.session; import com.vividsolutions.jts.geom.Geometry; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.apache.sis.feature.FeatureExt; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.FeatureStore; import org.geotoolkit.data.FeatureStoreUtilities; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.memory.GenericFilterFeatureIterator; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.query.QueryUtilities; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.factory.Hints; import org.geotoolkit.filter.visitor.DuplicatingFilterVisitor; import org.geotoolkit.filter.visitor.SimplifyingFilterVisitor; import org.geotoolkit.geometry.DefaultBoundingBox; import org.geotoolkit.geometry.jts.JTS; import org.apache.sis.referencing.CRS; import org.geotoolkit.version.Version; import org.opengis.util.GenericName; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.expression.Literal; import org.opengis.filter.identity.Identifier; import org.opengis.geometry.BoundingBox; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.apache.sis.geometry.Envelopes; import org.apache.sis.util.logging.Logging; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; /** * * @author Johann Sorel (Geomatys) * @module */ public class DefaultSession extends AbstractSession { protected static final FilterFactory2 FF = (FilterFactory2) FactoryFinder.getFilterFactory(new Hints(Hints.FILTER_FACTORY, FilterFactory2.class)); private final DefaultSessionDiff diff; private final boolean async; private final Version version; public DefaultSession(final FeatureStore store, final boolean async){ this(store,async,null); } public DefaultSession(final FeatureStore store, final boolean async, final Version version){ super(store); this.diff = createDiff(); this.async = async; this.version = version; } protected DefaultSessionDiff createDiff(){ return new DefaultSessionDiff(); } protected AddDelta createAddDelta(Session session, String typeName, Collection<? extends Feature> features){ return new AddDelta(this, typeName, features); } protected ModifyDelta createModifyDelta(Session session, String typeName, Id filter , final Map<String,?> values){ return new ModifyDelta(this, typeName, filter, values); } protected RemoveDelta createRemoveDelta(Session session, String typeName, Id filter){ return new RemoveDelta(session, typeName, filter); } /** * {@inheritDoc } */ @Override public boolean isAsynchrone() { return async; } /** * {@inheritDoc } */ @Override public Version getVersion() { return version; } /** * {@inheritDoc } */ @Override public FeatureCollection getFeatureCollection(final Query query) { return QueryUtilities.evaluate("id", query,this); } /** * {@inheritDoc } */ @Override public FeatureIterator getFeatureIterator(Query original) throws DataStoreException { //quick check to bypass deltas if(!hasPendingChanges()){ return store.getFeatureReader(original); } original = forceCRS(original,false); final List<Delta> deltas = diff.getDeltas(); //we must store the modified queries to iterate on them in reverse order. final List<Query> modifieds = new ArrayList<>(deltas.size()); Query modified = original; for(int i=0,n=deltas.size(); i<n; i++){ final Delta delta = deltas.get(i); modifieds.add(modified); //store before modification modified = delta.modify(modified); } FeatureIterator reader = store.getFeatureReader(modified); for(int i=0,n=deltas.size(); i<n; i++){ final Delta delta = deltas.get(i); reader = delta.modify(modifieds.get(i),reader); } //we must preserve the original filter after all thoses modifications Filter originalFilter = original.getFilter(); originalFilter = forceCRS(originalFilter, original.getCoordinateSystemReproject(), true); reader = GenericFilterFeatureIterator.wrap(reader, originalFilter); return reader; } /** * {@inheritDoc } */ @Override public void addFeatures(final String groupName, final Collection newFeatures) throws DataStoreException { checkVersion(); //will raise an error if the name doesnt exist store.getFeatureType(groupName); if(async){ diff.add(createAddDelta(this, groupName, newFeatures)); fireSessionChanged(); }else{ store.addFeatures(groupName.toString(), newFeatures); } } /** * {@inheritDoc } */ @Override public void updateFeatures(final String groupName, Filter filter, final Map<String,?> values) throws DataStoreException { checkVersion(); //will raise an error if the name doesnt exist store.getFeatureType(groupName); if(values == null || values.isEmpty()){ //no modifications, no need to create a modify delta //todo should we raise an error ? return; } if(async){ final Id modified; final SimplifyingFilterVisitor simplifier = new SimplifyingFilterVisitor(); filter = (Filter) filter.accept(simplifier, null); if(filter instanceof Id){ modified = FF.id( ((Id)filter).getIdentifiers()); }else{ final Set<Identifier> identifiers = new HashSet<Identifier>(); QueryBuilder qb = new QueryBuilder(groupName); qb.setFilter(filter); final FeatureIterator ite = getFeatureIterator(qb.buildQuery()); try{ while(ite.hasNext()){ identifiers.add(FeatureExt.getId(ite.next())); } }finally{ ite.close(); } if(identifiers.isEmpty()){ //no feature match this filter, no need to create a modify delta return; }else{ modified = FF.id(identifiers); } } diff.add(createModifyDelta(this, groupName, modified, values)); fireSessionChanged(); }else{ store.updateFeatures(groupName, filter, values); } } /** * {@inheritDoc } */ @Override public void removeFeatures(final String groupName, final Filter filter) throws DataStoreException { checkVersion(); //will raise an error if the name doesnt exist store.getFeatureType(groupName); if(async){ final Id removed; if(filter instanceof Id){ removed = (Id)filter; }else{ final Set<Identifier> identifiers = new HashSet<Identifier>(); QueryBuilder qb = new QueryBuilder(groupName); qb.setFilter(filter); final FeatureIterator ite = getFeatureIterator(qb.buildQuery()); try{ while(ite.hasNext()){ identifiers.add(FeatureExt.getId(ite.next())); } }finally{ ite.close(); } if(identifiers.isEmpty()){ //no feature match this filter, no need to create to remove delta return; }else{ removed = FF.id(identifiers); } } diff.add(createRemoveDelta(this, groupName, removed)); fireSessionChanged(); }else{ store.removeFeatures(groupName, filter); } } /** * {@inheritDoc } */ @Override public boolean hasPendingChanges() { return !diff.getDeltas().isEmpty(); } /** * {@inheritDoc } */ @Override public void commit() throws DataStoreException { diff.commit(store); fireSessionChanged(); } /** * {@inheritDoc } */ @Override public void rollback() { diff.rollback(); fireSessionChanged(); } /** * {@inheritDoc } */ @Override public long getCount(final Query original) throws DataStoreException { if(hasPendingChanges()){ return FeatureStoreUtilities.calculateCount(getFeatureIterator(original)); }else{ return store.getCount(original); } } /** * {@inheritDoc } */ @Override public Envelope getEnvelope(Query original) throws DataStoreException { if(hasPendingChanges()){ return FeatureStoreUtilities.calculateEnvelope(getFeatureIterator(original)); }else{ return store.getEnvelope(original); } } protected DefaultSessionDiff getDiff() { return diff; } /** * Test if a version is set, raise an error if it's the case. * Version must not be set when doing writing operations */ protected void checkVersion() throws DataStoreException { if(version!=null){ throw new DataStoreException("Session is opened on version : "+version+". " + "Writing operations are not allowed, open a session without version to support writing."); } } private Query forceCRS(final Query query, boolean replace) throws DataStoreException{ final FeatureType ft = store.getFeatureType(query.getTypeName()); final CoordinateReferenceSystem crs = FeatureExt.getCRS(ft); if(crs == null){ return query; } final QueryBuilder qb = new QueryBuilder(query); qb.setFilter(forceCRS(qb.getFilter(), crs,replace)); return qb.buildQuery(); } private static Filter forceCRS(final Filter filter, final CoordinateReferenceSystem crs, final boolean replace){ if(crs == null) return filter; final FilterVisitor visitor = new DuplicatingFilterVisitor(){ @Override public Object visit(final Literal expression, final Object extraData) { final Object obj = expression.getValue(); if(obj instanceof BoundingBox){ BoundingBox bb = (BoundingBox) obj; if(bb.getCoordinateReferenceSystem() == null){ //force crs definition bb = new DefaultBoundingBox(bb, crs); return FF.literal(bb); }else if(replace){ try { //reproject bbox final Envelope env = Envelopes.transform(bb, crs); bb = new DefaultBoundingBox(env); return FF.literal(bb); } catch (TransformException ex) { Logging.getLogger("org.geotoolkit.data.session").log(Level.SEVERE, null, ex); } } return expression; }else if(obj instanceof Geometry){ Geometry geom = (Geometry) obj; try { CoordinateReferenceSystem cdtcrs = JTS.findCoordinateReferenceSystem(geom); if(cdtcrs == null){ geom = (Geometry) geom.clone(); JTS.setCRS(geom, crs); return FF.literal(geom); }else if(replace){ //reproject geometry final MathTransform trs = CRS.findOperation(cdtcrs, crs, null).getMathTransform(); geom = JTS.transform(geom, trs); JTS.setCRS(geom, crs); return FF.literal(geom); } } catch (Exception ex) { Logging.getLogger("org.geotoolkit.data.session").log(Level.WARNING, ex.getLocalizedMessage(), ex); geom = (Geometry) geom.clone(); JTS.setCRS(geom, crs); return FF.literal(geom); } return expression; }else{ return super.visit(expression, extraData); } } }; return (Filter) filter.accept(visitor, null); } }