//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/ogcwebservices/wfs/TransactionHandler.java,v 1.65 2006/11/27 09:07:53 poth Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
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:
Andreas Poth
lat/lon GmbH
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.ogcwebservices.wfs;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.deegree.datatypes.QualifiedName;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.xml.XMLParsingException;
import org.deegree.framework.xml.XMLTools;
import org.deegree.i18n.Messages;
import org.deegree.io.datastore.Datastore;
import org.deegree.io.datastore.DatastoreException;
import org.deegree.io.datastore.DatastoreTransaction;
import org.deegree.io.datastore.FeatureId;
import org.deegree.io.datastore.PropertyPathResolver;
import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
import org.deegree.io.datastore.schema.MappedFeatureType;
import org.deegree.io.datastore.schema.MappedGMLSchema;
import org.deegree.model.crs.UnknownCRSException;
import org.deegree.model.feature.Feature;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.feature.FeatureProperty;
import org.deegree.model.feature.GMLFeatureDocument;
import org.deegree.model.feature.Validator;
import org.deegree.model.feature.schema.FeatureType;
import org.deegree.model.feature.schema.GMLSchema;
import org.deegree.model.feature.schema.PropertyType;
import org.deegree.ogcbase.PropertyPath;
import org.deegree.ogcbase.PropertyPathStep;
import org.deegree.ogcwebservices.OGCWebServiceException;
import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
import org.deegree.ogcwebservices.wfs.operation.transaction.InsertResults;
import org.deegree.ogcwebservices.wfs.operation.transaction.Native;
import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation;
import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Handler for transaction requests to the {@link WFService}.
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
* @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh </a>
* @author last edited by: $Author: poth $
*
* @version $Revision: 1.65 $, $Date: 2006/11/27 09:07:53 $
*/
public class TransactionHandler {
private static final ILogger LOG = LoggerFactory.getLogger( TransactionHandler.class );
private WFService service;
private Transaction request;
private Map<QualifiedName, MappedFeatureType> ftMap;
// filled in #acquireDSTransactions()
private Map<QualifiedName, DatastoreTransaction> taMap = new HashMap<QualifiedName, DatastoreTransaction>();
// filled in #acquireDSTransactions()
private Map<Datastore, DatastoreTransaction> dsToTaMap = new HashMap<Datastore, DatastoreTransaction>();
/**
* Creates a new <code>TransactionHandler</code> instance.
*
* @param service
* @param request
*/
public TransactionHandler( WFService service, Transaction request ) {
this.service = service;
this.request = request;
this.ftMap = service.getMappedFeatureTypes();
}
/**
* Performs the associated transaction.
*
* @return transaction response
* @throws OGCWebServiceException
* if an error occured
*/
public TransactionResponse handleRequest()
throws OGCWebServiceException {
try {
validate( this.request );
} catch ( UnknownCRSException e1 ) {
throw new OGCWebServiceException( getClass().getName(), e1.getMessage() );
}
TransactionResponse response = null;
acquireDSTransactions();
try {
try {
response = performOperations();
} catch ( OGCWebServiceException e ) {
abortDSTransactions();
throw e;
}
commitDSTransactions();
} finally {
releaseDSTransactions();
}
return response;
}
/**
* Validates the feature instances in the given transaction against the WFS' application
* schemas.
* <p>
* The feature instances are assigned the corresponding <code>MappedFeatureType</code> in the
* process.
*
* @param request
* @throws OGCWebServiceException
* @throws UnknownCRSException
*/
private void validate( Transaction request )
throws OGCWebServiceException, UnknownCRSException {
List<TransactionOperation> operations = request.getOperations();
Iterator<TransactionOperation> iter = operations.iterator();
while ( iter.hasNext() ) {
TransactionOperation operation = iter.next();
if ( operation instanceof Insert ) {
validateInsert( (Insert) operation );
} else if ( operation instanceof Delete ) {
// nothing to do (contains no features)
} else if ( operation instanceof Update ) {
validateUpdate( (Update) operation );
} else if ( operation instanceof Native ) {
// nothing to do (contains no features)
} else {
String msg = "Internal error. Unhandled transaction operation type '"
+ operation.getClass().getName() + "'.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
}
/**
* Validates all feature instances in the given insert operation against the WFS' application
* schemas.
* <p>
* The feature instances are assigned the corresponding <code>MappedFeatureType</code> in the
* process.
*
* @param operation
* @throws OGCWebServiceException
*/
private void validateInsert( Insert operation )
throws OGCWebServiceException {
FeatureCollection fc = operation.getFeatures();
Validator validator = new Validator( (Map) this.service.getMappedFeatureTypes() );
for ( int i = 0; i < fc.size(); i++ ) {
validator.validate( fc.getFeature( i ) );
}
}
/**
* Validates any feature instance in the given update operation against the WFS' application
* schemas.
* <p>
* Feature instances are assigned the corresponding <code>MappedFeatureType</code> in the
* process, property names are normalized and their values are parsed into the respective
* objects.
*
* @param operation update operation
* @throws OGCWebServiceException
* @throws UnknownCRSException
*/
private void validateUpdate( Update operation )
throws OGCWebServiceException, UnknownCRSException {
QualifiedName ftName = operation.getTypeName();
MappedFeatureType ft = this.ftMap.get( ftName );
if ( ft == null ) {
String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_TYPE_UNKNOWN", ftName );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
Feature feature = operation.getFeature();
if ( feature != null ) {
Validator validator = new Validator( (Map) this.service.getMappedFeatureTypes() );
validator.validate( feature );
} else {
validateProperties( ft, operation );
}
}
/**
* Validates the properties and their replacement values that are specified in the given
* <code>Update</code> operation.
* <p>
* Property names are normalized and their values are parsed into the respective objects.
*
* @param ft feature type
* @param operation update operation
* @throws OGCWebServiceException
* @throws UnknownCRSException
*/
private void validateProperties( MappedFeatureType ft, Update operation )
throws OGCWebServiceException, UnknownCRSException {
Map<PropertyPath, Node> rawProperties = operation.getRawProperties();
Map<PropertyPath, Object> parsedProperties = new LinkedHashMap<PropertyPath, Object>();
for ( PropertyPath path : rawProperties.keySet() ) {
Node propertyValue = rawProperties.get( path );
path = PropertyPathResolver.normalizePropertyPath( ft, path );
Object property = validateProperty( ft, path, propertyValue );
parsedProperties.put( path, property );
}
operation.setParsedProperties( parsedProperties );
}
/**
* Validates the properties and their replacement values that are specified in the given
* <code>Update</code> operation.
* <p>
* Values are parsed into the respective objects.
*
* @param ft feature type
* @param path property name
* @param value replacement property value (as XML node)
* @return object representation of the replacement property value
* @throws OGCWebServiceException
* @throws UnknownCRSException
*/
private Object validateProperty( MappedFeatureType ft, PropertyPath path, Node value )
throws OGCWebServiceException, UnknownCRSException {
Object propertyValue = null;
for ( int i = 0; i < path.getSteps(); i += 2 ) {
// check if feature step is valid
PropertyPathStep ftStep = path.getStep( i );
FeatureType stepFt = this.ftMap.get( ftStep.getPropertyName() );
if ( stepFt == null ) {
String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_STEP_UNKNOWN", path,
stepFt.getName() );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
MappedGMLSchema schema = ft.getGMLSchema();
if ( !schema.isValidSubstitution( ft, stepFt ) ) {
String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_STEP_INVALID", path,
stepFt.getName(), ft.getName() );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
// check if property step is valid
PropertyPathStep propertyStep = path.getStep( i + 1 );
QualifiedName propertyName = propertyStep.getPropertyName();
PropertyType pt = ft.getProperty( propertyName );
if ( pt == null ) {
String msg = Messages.getMessage( "WFS_UPDATE_PROPERTY_STEP_UNKNOWN", path,
propertyName, ft.getName() );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
if ( i + 2 == path.getSteps() ) {
if ( value != null ) {
try {
// create a wrapper document for the new property value
GMLFeatureDocument propertyDoc = new GMLFeatureDocument();
Map<URI, GMLSchema> nsToSchemaMap = buildNsToSchemaMap();
propertyDoc.setSchemas( nsToSchemaMap );
Document wrapperDoc = XMLTools.create();
Element root = wrapperDoc.createElementNS(
propertyName.getNamespace().toString(),
propertyName.getAsString() );
wrapperDoc.appendChild( root );
XMLTools.appendNSBinding( root, propertyName.getPrefix(),
propertyName.getNamespace() );
propertyDoc.setRootElement( root );
root.appendChild( wrapperDoc.importNode( value, true ) );
FeatureProperty replacementProperty = null;
replacementProperty = propertyDoc.parseProperty( root, ft );
propertyValue = replacementProperty.getValue();
} catch ( XMLParsingException e ) {
e.printStackTrace();
String msg = Messages.getMessage( "WFS_UPDATE_PROPERTY_VALUE_INVALID", path,
e.getMessage() );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
} else {
if ( !( pt instanceof MappedFeaturePropertyType ) ) {
String msg = Messages.getMessage( "WFS_UPDATE_NOT_FEATURE_PROPERTY", path,
propertyName );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
MappedFeaturePropertyType fpt = (MappedFeaturePropertyType) pt;
ft = fpt.getFeatureTypeReference().getFeatureType();
}
}
return propertyValue;
}
private Map<URI, GMLSchema> buildNsToSchemaMap() {
Map<URI, GMLSchema> nsToSchemaMap = new HashMap<URI, GMLSchema>();
Set<GMLSchema> schemas = new HashSet<GMLSchema>();
for ( MappedFeatureType ft : this.ftMap.values() ) {
schemas.add( ft.getGMLSchema() );
}
for ( GMLSchema schema : schemas ) {
nsToSchemaMap.put( schema.getTargetNamespace(), schema );
}
return nsToSchemaMap;
}
/**
* Performs the operations contained in the transaction.
*
* @throws OGCWebServiceException
*/
private TransactionResponse performOperations()
throws OGCWebServiceException {
int inserts = 0;
int deletes = 0;
int updates = 0;
List<InsertResults> insertResults = new ArrayList<InsertResults>();
List<TransactionOperation> operations = request.getOperations();
Iterator<TransactionOperation> iter = operations.iterator();
while ( iter.hasNext() ) {
TransactionOperation operation = iter.next();
String handle = operation.getHandle();
try {
if ( operation instanceof Insert ) {
List<FeatureId> insertedFIDs = performInsert( (Insert) operation );
InsertResults results = new InsertResults( handle, insertedFIDs );
insertResults.add( results );
inserts += insertedFIDs.size();
} else if ( operation instanceof Delete ) {
deletes += performDelete( (Delete) operation );
} else if ( operation instanceof Update ) {
updates += performUpdate( (Update) operation );
} else if ( operation instanceof Native ) {
String msg = Messages.getMessage( "WFS_NATIVE_OPERATIONS_UNSUPPORTED" );
throw new OGCWebServiceException( this.getClass().getName(), msg );
} else {
String opType = operation.getClass().getName();
String msg = Messages.getMessage( "WFS_UNHANDLED_OPERATION_TYPE", opType );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
} catch ( DatastoreException e ) {
LOG.logDebug( e.getMessage(), e );
String msg = "A datastore exception occured during the processing of operation with handle '"
+ handle + "': " + e.getMessage();
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
TransactionResponse response = new TransactionResponse( request, inserts, updates, deletes,
insertResults );
return response;
}
/**
* Performs the given insert operation.
*
* @param insert
* insert operation to be performed
* @throws DatastoreException
*/
private List<FeatureId> performInsert( Insert insert )
throws DatastoreException {
List<FeatureId> fids = new ArrayList<FeatureId>();
FeatureCollection fc = insert.getFeatures();
// merge all equal and anonymous features (without fid)
FeatureDisambiguator merger = new FeatureDisambiguator( fc );
fc = merger.mergeFeatures();
Map<DatastoreTransaction, List<Feature>> taFeaturesMap = new HashMap<DatastoreTransaction, List<Feature>>();
FeatureIdAssigner fidAssigner = new FeatureIdAssigner( insert.getIdGen() );
// assign each feature instance to the corresponding transaction (and datastore)
for ( int i = 0; i < fc.size(); i++ ) {
Feature feature = fc.getFeature( i );
QualifiedName ftName = feature.getName();
//MappedFeatureType ft = this.ftMap.get(ftName);
DatastoreTransaction dsTa = this.taMap.get( ftName );
// reassign feature ids (if necessary)
fidAssigner.assignFID( feature, dsTa );
List<Feature> features = taFeaturesMap.get( dsTa );
if ( features == null ) {
features = new ArrayList<Feature>();
taFeaturesMap.put( dsTa, features );
}
features.add( feature );
}
// TODO remove this hack
fidAssigner.markStoredFeatures();
Iterator<DatastoreTransaction> taIter = taFeaturesMap.keySet().iterator();
while ( taIter.hasNext() ) {
DatastoreTransaction ta = taIter.next();
List<Feature> features = taFeaturesMap.get( ta );
fids.addAll( ta.performInsert( features ) );
}
return fids;
}
/**
* Performs the given delete operation.
*
* @param delete
* delete operation to be performed
* @throws DatastoreException
*/
private int performDelete( Delete delete )
throws DatastoreException {
QualifiedName ftName = delete.getTypeName();
MappedFeatureType ft = this.ftMap.get( ftName );
DatastoreTransaction dsTa = this.taMap.get( ftName );
int deleted = dsTa.performDelete( ft, delete.getFilter() );
return deleted;
}
/**
* Performs the given update operation.
* <p>
* Assigning of FIDs to replacment features is performed in the <code>UpdateHandler</code>.
*
* @param update
* update operation to be perform
* @throws DatastoreException
*/
private int performUpdate( Update update )
throws DatastoreException {
QualifiedName ftName = update.getTypeName();
MappedFeatureType ft = this.ftMap.get( ftName );
DatastoreTransaction dsTa = this.taMap.get( ftName );
int updated = 0;
if ( update.getFeature() == null ) {
updated = dsTa.performUpdate( ft, update.getReplacementProperties(), update.getFilter() );
} else {
updated = dsTa.performUpdate( ft, update.getFeature(), update.getFilter() );
}
return updated;
}
/**
* Acquires the necessary <code>DatastoreTransaction</code>s. For each participating
* <code>Datastore</code>, one transaction is needed.
* <p>
* Fills the taMap and dsToTaMap members of this class.
*
* @throws OGCWebServiceException
* if a feature type is unknown or a DatastoreTransaction could not be acquired
*/
private void acquireDSTransactions()
throws OGCWebServiceException {
Set<QualifiedName> ftNames = this.request.getAffectedFeatureTypes();
for ( QualifiedName ftName : ftNames ) {
MappedFeatureType ft = this.ftMap.get( ftName );
if ( ft == null ) {
String msg = "FeatureType '" + ftName + "' is not known to the WFS.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
Datastore ds = ft.getGMLSchema().getDatastore();
DatastoreTransaction dsTa = this.dsToTaMap.get( ds );
if ( dsTa == null ) {
try {
dsTa = ds.acquireTransaction();
} catch ( DatastoreException e ) {
String msg = "Could not acquire transaction for FeatureType '" + ftName + "'.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
this.dsToTaMap.put( ds, dsTa );
}
this.taMap.put( ftName, dsTa );
}
}
/**
* Releases all acquired <code>DatastoreTransaction</code>s.
*
* @throws OGCWebServiceException if a DatastoreTransaction could not be released
*/
private void releaseDSTransactions()
throws OGCWebServiceException {
String msg = "";
for ( DatastoreTransaction dsTa : this.taMap.values() ) {
LOG.logDebug( "Releasing DatastoreTransaction." );
try {
dsTa.release();
} catch ( DatastoreException e ) {
LOG.logError( "Error releasing DatastoreTransaction: " + e.getMessage(), e );
msg += e.getMessage();
}
}
if ( msg.length() != 0 ) {
msg = "Could not release one or more DatastoreTransactions: " + msg;
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
/**
* Commits all pending <code>DatastoreTransaction</code>s.
*
* @throws OGCWebServiceException
* if a DatastoreException could not be committed
*/
private void commitDSTransactions()
throws OGCWebServiceException {
Iterator<DatastoreTransaction> iter = this.dsToTaMap.values().iterator();
while ( iter.hasNext() ) {
DatastoreTransaction dsTa = iter.next();
try {
dsTa.commit();
} catch ( DatastoreException e ) {
String msg = "Could not commit transaction.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
}
/**
* Aborts all pending <code>DatastoreTransaction</code>s.
*
* @throws OGCWebServiceException
* if a DatastoreException could not be aborted
*/
private void abortDSTransactions()
throws OGCWebServiceException {
Iterator<DatastoreTransaction> iter = this.dsToTaMap.values().iterator();
while ( iter.hasNext() ) {
DatastoreTransaction dsTa = iter.next();
try {
dsTa.rollback();
} catch ( DatastoreException e ) {
String msg = "Could not abort transaction.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: TransactionHandler.java,v $
Revision 1.65 2006/11/27 09:07:53 poth
JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code.
Revision 1.64 2006/11/16 08:53:21 mschneider
Merged messages from org.deegree.ogcwebservices.wfs and its subpackages.
Revision 1.63 2006/10/10 08:07:59 poth
Collection substituted by List
Revision 1.62 2006/10/09 09:01:48 poth
bug fix - TransactionResponse extending DefaultOGCWebServiceResponse
Revision 1.61 2006/10/01 11:15:43 poth
trigger points for doService methods defined
Revision 1.60 2006/08/22 18:14:42 mschneider
Refactored due to cleanup of org.deegree.io.datastore.schema package.
Revision 1.59 2006/08/21 15:49:15 mschneider
Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package.
Revision 1.58 2006/08/14 13:17:25 mschneider
Does not longer extend AbstractHandler. Javadoc fixes.
Revision 1.57 2006/08/01 10:42:23 mschneider
Adapted because of changes in FeatureDisambiguator.
Revision 1.56 2006/07/27 16:21:13 mschneider
Fixed formatting.
Revision 1.55 2006/07/25 15:50:53 mschneider
Outfactored disambiguation of features into class FeatureDisambiguator.
Revision 1.54 2006/07/13 07:27:26 poth
*** empty log message ***
Revision 1.53 2006/07/10 08:26:02 poth
allow geometry fields to be identity part
Revision 1.52 2006/06/29 10:28:14 mschneider
Moved identifying of stored features / assigning of feature ids to UpdateHandler.
Revision 1.51 2006/06/29 07:05:04 poth
*** empty log message ***
Revision 1.50 2006/06/28 08:52:51 poth
bug fixes according catalog harvesting
Revision 1.49 2006/05/24 15:25:45 mschneider
Added feature id assigning for replacement features (update).
Revision 1.48 2006/05/23 22:39:26 mschneider
Updates are now handled correctly.
Revision 1.47 2006/05/23 21:25:41 mschneider
Removed System.out.println() calls.
Revision 1.46 2006/05/23 16:10:37 mschneider
Added functionality to validate properties in Update operations.
Revision 1.45 2006/05/18 15:45:57 mschneider
Added handling of non-standard update (feature replace).
Revision 1.44 2006/05/16 16:22:13 mschneider
Refactored due to the splitting of org.deegree.ogcwebservices.wfs.operation package.
Added validation for features in update operations.
********************************************************************** */