/* (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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.wfs.request.Property;
import org.geoserver.wfs.request.TransactionElement;
import org.geoserver.wfs.request.TransactionRequest;
import org.geoserver.wfs.request.TransactionResponse;
import org.geoserver.wfs.request.Update;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureLocking;
import org.geotools.data.FeatureStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureLocking;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.projection.PointOutsideEnvelopeException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Id;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.identity.FeatureId;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import com.vividsolutions.jts.geom.Geometry;
/**
* Processes standard update elements
*
* @author Andrea Aime - TOPP
*
*/
public class UpdateElementHandler extends AbstractTransactionElementHandler {
/**
* logger
*/
static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wfs");
public UpdateElementHandler(GeoServer gs) {
super(gs);
}
public void checkValidity(TransactionElement element, Map<QName, FeatureTypeInfo> typeInfos)
throws WFSTransactionException {
// check inserts are enabled
if (!getInfo().getServiceLevel().getOps().contains(WFSInfo.Operation.TRANSACTION_UPDATE) ) {
throw new WFSException(element, "Transaction Update support is not enabled");
}
Update update = (Update) element;
FilterFactory ff = CommonFactoryFinder.getFilterFactory( null );
try {
FeatureTypeInfo meta = typeInfos.values().iterator().next();
FeatureType featureType = meta.getFeatureType();
List<Property> props = update.getUpdateProperties();
for (Iterator<Property> prop = props.iterator(); prop.hasNext();) {
Property property = prop.next();
//check that valus that are non-nillable exist
if (property.getValue() == null) {
String propertyName = property.getName().getLocalPart();
AttributeDescriptor attributeType = null;
PropertyDescriptor pd = featureType.getDescriptor(propertyName);
if(pd instanceof AttributeDescriptor) {
attributeType = (AttributeDescriptor) pd;
}
if ((attributeType != null) && (attributeType.getMinOccurs() > 0)) {
String msg = "Property '" + attributeType.getLocalName()
+ "' is mandatory but no value specified.";
throw new WFSException(element, msg, "MissingParameterValue");
}
}
//check that property names are actually valid
QName name = property.getName();
PropertyName propertyName = null;
if ( name.getPrefix() != null && !"".equals( name.getPrefix() )) {
propertyName = ff.property( name.getPrefix() + ":" + name.getLocalPart() );
}
else {
propertyName = ff.property( name.getLocalPart() );
}
if ( propertyName.evaluate( featureType ) == null ) {
String msg = "No such property: " + name;
throw new WFSException(element, msg );
}
}
} catch (IOException e) {
throw new WFSTransactionException("Could not locate feature type information for " +
update.getTypeName(), e, update.getHandle());
}
}
public void execute(TransactionElement element, TransactionRequest request,
@SuppressWarnings("rawtypes") Map<QName, FeatureStore> featureStores,
TransactionResponse response, TransactionListener listener) throws WFSTransactionException {
Update update = (Update) element;
final QName elementName = update.getTypeName();
String handle = update.getHandle();
long updated = response.getTotalUpdated().longValue();
SimpleFeatureStore store = DataUtilities.simple((FeatureStore) featureStores.get(elementName));
if (store == null) {
throw new WFSException(request, "Could not locate FeatureStore for '" + elementName + "'");
}
LOGGER.finer("Transaction Update:" + update);
try {
Filter filter = update.getFilter();
// make sure all geometric elements in the filter have a crs, and that the filter
// is reprojected to store's native crs as well
CoordinateReferenceSystem declaredCRS = WFSReprojectionUtil.getDeclaredCrs(
store.getSchema(), request.getVersion());
if(filter != null) {
filter = WFSReprojectionUtil.normalizeFilterCRS(filter, store.getSchema(), declaredCRS);
} else {
filter = Filter.INCLUDE;
}
List<Property> properties = update.getUpdateProperties();
AttributeDescriptor[] types = new AttributeDescriptor[properties.size()];
String[] names = new String[properties.size()];
Object[] values = new Object[properties.size()];
for (int j = 0; j < properties.size(); j++) {
Property property = properties.get(j);
QName propertyName = property.getName();
types[j] = store.getSchema().getDescriptor(propertyName.getLocalPart());
names[j] = propertyName.getLocalPart();
values[j] = property.getValue();
// if geometry, it may be necessary to reproject it to the native CRS before
// update
if (values[j] instanceof Geometry ) {
Geometry geometry = (Geometry) values[j];
// get the source crs, check the geometry itself first. If not set, assume
// the default one
CoordinateReferenceSystem source = null;
if ( geometry.getUserData() instanceof CoordinateReferenceSystem ) {
source = (CoordinateReferenceSystem) geometry.getUserData();
} else {
geometry.setUserData(declaredCRS);
source = declaredCRS;
}
// see if the geometry has a CRS other than the default one
CoordinateReferenceSystem target = null;
if (types[j] instanceof GeometryDescriptor) {
target = ((GeometryDescriptor)types[j]).getCoordinateReferenceSystem();
}
if(getInfo().isCiteCompliant())
JTS.checkCoordinatesRange(geometry, source != null ? source : target);
//if we have a source and target and they are not equal, do
// the reprojection, otherwise just update the value as is
if ( source != null && target != null && !CRS.equalsIgnoreMetadata(source, target)) {
try {
//TODO: this code should be shared with the code
// from ReprojectingFeatureCollection --JD
MathTransform tx = CRS.findMathTransform(source, target, true);
GeometryCoordinateSequenceTransformer gtx =
new GeometryCoordinateSequenceTransformer();
gtx.setMathTransform(tx);
values[j] = gtx.transform(geometry);
}
catch( Exception e ) {
String msg = "Failed to reproject geometry:" + e.getLocalizedMessage();
throw new WFSTransactionException( msg, e );
}
}
}
}
// Pass through data to collect fids and damaged
// region
// for validation
//
Set<FeatureId> fids = new HashSet<FeatureId>();
LOGGER.finer("Preprocess to remember modification as a set of fids");
SimpleFeatureCollection features = store.getFeatures(filter);
TransactionEvent event = new TransactionEvent(TransactionEventType.PRE_UPDATE, request,
elementName, features);
event.setSource(Update.WFS11.unadapt(update));
listener.dataStoreChange( event );
FeatureIterator preprocess = features.features();
try {
while (preprocess.hasNext()) {
SimpleFeature feature = (SimpleFeature) preprocess.next();
fids.add(feature.getIdentifier());
}
} catch (NoSuchElementException e) {
throw new WFSException(request, "Could not aquire FeatureIDs", e);
} finally {
preprocess.close();
}
try {
store.modifyFeatures(names, values, filter);
} catch( Exception e) {
//JD: this is a bit hacky but some of the wfs cite tests require
// that the 'InvalidParameterValue' code be set on exceptions in
// cases where a "bad" value is being suppliedin an update, so
// we always set to that code
throw new WFSTransactionException( "Update error: " + e.getMessage(), e, "InvalidParameterValue");
}
finally {
// make sure we unlock
if ((request.getLockId() != null) && store instanceof FeatureLocking
&& (request.isReleaseActionSome())) {
SimpleFeatureLocking locking;
locking = (SimpleFeatureLocking) store;
locking.unLockFeatures(filter);
}
}
// Post process - gather the same features after the update, and
if (!fids.isEmpty()) {
LOGGER.finer("Post process update for boundary update and featureValidation");
Set<FeatureId> featureIds = new HashSet<FeatureId>();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
for (Iterator<FeatureId> f = fids.iterator(); f.hasNext();) {
// create new FeatureIds without any possible version information in order to
// query for the latest version
featureIds.add(ff.featureId(f.next().getID()));
}
Id modified = ff.id(featureIds);
SimpleFeatureCollection changed = store.getFeatures(modified);
// grab final ids. Not using fetureIds as they may contain different version
// information after the update
Set<FeatureId> changedIds = new HashSet<FeatureId>();
SimpleFeatureIterator iterator = changed.features();
try{
while(iterator.hasNext()){
changedIds.add(iterator.next().getIdentifier());
}
}finally{
iterator.close();
}
response.addUpdatedFeatures(handle, changedIds);
listener.dataStoreChange(new TransactionEvent(TransactionEventType.POST_UPDATE,
request, elementName, changed, Update.WFS11.unadapt(update)));
}
// update the update counter
updated += fids.size();
} catch (IOException ioException) {
// JD: changing from throwing service exception to
// adding action that failed
throw new WFSTransactionException(ioException, null, handle);
} catch(PointOutsideEnvelopeException poe) {
throw new WFSTransactionException(poe, null, handle);
}
// update transaction summary
response.setTotalUpdated(BigInteger.valueOf(updated));
}
/**
* @see org.geoserver.wfs.TransactionElementHandler#getElementClass()
*/
public Class getElementClass() {
return Update.class;
}
/**
* @see org.geoserver.wfs.TransactionElementHandler#getTypeNames(org.eclipse.emf.ecore.EObject)
*/
public QName[] getTypeNames(TransactionElement element) throws WFSTransactionException {
return new QName[] { element.getTypeName() };
}
}