/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geoserver.config.GeoServer;
import org.geoserver.wfs.request.Replace;
import org.geoserver.wfs.request.TransactionElement;
import org.geoserver.wfs.request.TransactionRequest;
import org.geoserver.wfs.request.TransactionResponse;
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.Id;
import org.opengis.filter.identity.FeatureId;
public class ReplaceElementHandler extends AbstractTransactionElementHandler {
static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wfs");
static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null);
public ReplaceElementHandler(GeoServer geoServer) {
super(geoServer);
}
public Class getElementClass() {
return Replace.class;
}
public QName[] getTypeNames(TransactionElement element) throws WFSTransactionException {
Replace replace = (Replace) element;
List<QName> typeNames = new ArrayList();
List features = replace.getFeatures();
if (!features.isEmpty()) {
for (Iterator f = features.iterator(); f.hasNext();) {
SimpleFeature feature = (SimpleFeature) f.next();
String name = feature.getFeatureType().getTypeName();
String namespaceURI = feature.getFeatureType().getName().getNamespaceURI();
typeNames.add(new QName(namespaceURI, name));
}
}
return (QName[]) typeNames.toArray(new QName[typeNames.size()]);
}
public void checkValidity(TransactionElement element, Map featureTypeInfos)
throws WFSTransactionException {
if (!getInfo().getServiceLevel().getOps().contains( WFSInfo.Operation.TRANSACTION_REPLACE)) {
throw new WFSException(element, "Transaction REPLACE support is not enabled");
}
if (featureTypeInfos.size() != 1) {
throw new WFSException(element, "Transaction REPLACE must only specify features from a"+
" single feature type");
}
}
public void execute(TransactionElement element, TransactionRequest request, Map featureStores,
TransactionResponse response, TransactionListener listener) throws WFSTransactionException {
Replace replace = (Replace) element;
List<SimpleFeature> newFeatures = replace.getFeatures();
SimpleFeatureStore featureStore =
DataUtilities.simple((FeatureStore) featureStores.values().iterator().next());
if (featureStore == null) {
throw new WFSException(element, "Could not obtain feature store");
}
//check the supplied filter matches the number of supplied features
String featureTypeName = featureStore.getSchema().getTypeName();
//ids of replaced featres
Collection<FeatureId> replaced = new ArrayList();
try {
Query query = new Query(featureTypeName, replace.getFilter());
SimpleFeatureCollection features = featureStore.getFeatures(replace.getFilter());
if (newFeatures.size() != features.size()) {
throw new WFSException(element, String.format("Specified filter matched %d features but " +
"%d were supplied", features.size(), newFeatures.size()));
}
//replace the features in order...
//JD, TODO: a better mechanism for replace... this is sort of a hack based on a combo of
// feature ids and orders
// may want to check if the store is making feature id's writable and attempt
// to actually update the ID's
//load all the existing features into memory
Map<String,SimpleFeature> oldFeatures = new LinkedHashMap<String, SimpleFeature>();
SimpleFeatureIterator it = features.features();
try {
while(it.hasNext()) {
SimpleFeature f = it.next();
oldFeatures.put(f.getID(), f);
}
}
finally {
it.close();
}
//first pass update all the features that match by id
List<SimpleFeature> leftovers = new ArrayList();
for (SimpleFeature newFeature : newFeatures) {
SimpleFeature oldFeature = oldFeatures.get(newFeature.getID());
if (oldFeature == null) {
leftovers.add(newFeature);
continue;
}
//matching feature found, update it
replace(oldFeature, newFeature, featureStore, oldFeatures, replaced);
}
//do left overs
for (SimpleFeature newFeature : leftovers) {
//grab the "next" old feature
SimpleFeature oldFeature = oldFeatures.values().iterator().next();
replace(oldFeature, newFeature, featureStore, oldFeatures, replaced);
}
}
catch(IOException e) {
throw new WFSException(element, "Transaction REPLACE failed", e);
}
response.setTotalReplaced(BigInteger.valueOf(replaced.size()));
response.addReplacedFeatures(replace.getHandle(), replaced);
}
void replace(SimpleFeature oldFeature, SimpleFeature newFeature, SimpleFeatureStore featureStore,
Map<String,SimpleFeature> oldFeatures, Collection<FeatureId> ids)
throws IOException {
String[] names = new String[oldFeature.getAttributeCount()];
Object[] valus = new Object[names.length];
int i = 0;
for (AttributeDescriptor att : oldFeature.getType().getAttributeDescriptors()) {
String name = att.getLocalName();
Object valu = newFeature.getAttribute(name);
names[i] = name;
valus[i++] = valu;
}
FeatureId id = filterFactory.featureId(oldFeature.getID());
featureStore.modifyFeatures(names, valus, filterFactory.id(Collections.singleton(id)));
ids.add(id);
oldFeatures.remove(oldFeature.getID());
}
}