//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/idgenerator/FeatureIdAssigner.java,v 1.31 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.io.datastore.idgenerator;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
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.util.TimeTools;
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.schema.MappedFeatureType;
import org.deegree.io.datastore.schema.MappedGMLId;
import org.deegree.io.datastore.schema.MappedPropertyType;
import org.deegree.io.datastore.schema.MappedSimplePropertyType;
import org.deegree.io.datastore.schema.content.MappingField;
import org.deegree.io.datastore.schema.content.SimpleContent;
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.filterencoding.ComplexFilter;
import org.deegree.model.filterencoding.Filter;
import org.deegree.model.filterencoding.Literal;
import org.deegree.model.filterencoding.LogicalOperation;
import org.deegree.model.filterencoding.Operation;
import org.deegree.model.filterencoding.OperationDefines;
import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
import org.deegree.model.filterencoding.PropertyName;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.ogcbase.CommonNamespaces;
import org.deegree.ogcbase.PropertyPath;
import org.deegree.ogcbase.PropertyPathFactory;
import org.deegree.ogcwebservices.wfs.operation.GetFeature;
import org.deegree.ogcwebservices.wfs.operation.Query;
import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
/**
* Responsible for the assigning of valid {@link FeatureId}s which are a prerequisite to
* the insertion of features in a {@link Datastore}.
* <p>
* Prior to the assigning of new feature ids, "equal" features are looked up in the datastore and
* their feature ids are used.
*
* @see DatastoreTransaction#performInsert(List)
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
* @author last edited by: $Author: poth $
*
* @version $Revision: 1.31 $, $Date: 2006/11/27 09:07:53 $
*/
public class FeatureIdAssigner {
/** if an assigned feature id starts with this, it is already stored */
public static final String EXIST_PREFIX = "!";
private static final ILogger LOG = LoggerFactory.getLogger( FeatureIdAssigner.class );
private Map<String, FeatureId> oldFid2NewFidMap = new HashMap<String, FeatureId>();
private Set<Feature> reassignedFeatures = new HashSet<Feature>();
private Set<Feature> storedFeatures = new HashSet<Feature>();
private ID_GEN idGenMode;
/**
* Creates a new <code>FeatureIdAssigner</code> instance that generates new feature ids as
* specified.
*
* @param idGenMode
*/
public FeatureIdAssigner( ID_GEN idGenMode ) {
this.idGenMode = idGenMode;
}
/**
* Assigns valid {@link FeatureId}s to the given feature instance and it's subfeatures.
*
* @param feature
* @param ta
* @throws IdGenerationException
*/
public void assignFID( Feature feature, DatastoreTransaction ta )
throws IdGenerationException {
identifyStoredFeatures( feature, ta, new HashSet<Feature>() );
switch ( this.idGenMode ) {
case GENERATE_NEW: {
assignNewFIDs( feature, null, ta );
break;
}
case REPLACE_DUPLICATE: {
break;
}
case USE_EXISTING: {
break;
}
default: {
throw new IdGenerationException( "Internal error: Unhandled fid generation mode: "
+ this.idGenMode );
}
}
}
/**
* TODO mark stored features a better way
*/
public void markStoredFeatures() {
// hack: mark stored features (with "!")
for ( Feature f : this.storedFeatures ) {
String fid = f.getId();
if ( !fid.startsWith( EXIST_PREFIX ) ) {
f.setId( EXIST_PREFIX + fid );
}
}
}
private String identifyStoredFeatures( Feature feature, DatastoreTransaction ta,
Set<Feature> inProcessing )
throws IdGenerationException {
if ( this.reassignedFeatures.contains( feature ) ) {
return feature.getId();
}
inProcessing.add( feature );
boolean maybeEqual = true;
String existingFID = null;
LOG.logDebug( "Checking for existing feature that equals feature with type: '"
+ feature.getName() + "' and fid: '" + feature.getId() + "'." );
// build the comparison operations that are needed to select "equal" feature instances
List<Operation> compOperations = new ArrayList<Operation>();
FeatureProperty[] properties = feature.getProperties();
MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
for ( int i = 0; i < properties.length; i++ ) {
QualifiedName propertyName = properties[i].getName();
MappedPropertyType propertyType = (MappedPropertyType) ft.getProperty( propertyName );
Object propertyValue = properties[i].getValue();
if ( propertyValue instanceof Feature ) {
if ( inProcessing.contains( propertyValue ) ) {
LOG.logDebug( "Stopping recursion at property with '" + propertyName
+ "'. Cycle detected." );
continue;
}
LOG.logDebug( "Recursing on feature property: " + properties[i].getName() );
String subFeatureId = identifyStoredFeatures( (Feature) propertyValue, ta,
inProcessing );
if ( propertyType.isIdentityPart() ) {
if ( subFeatureId == null ) {
maybeEqual = false;
} else {
LOG.logDebug( "Need to check for feature property '" + propertyName
+ "' with fid '" + subFeatureId + "'." );
// build path that selects subfeature 'gml:id' attribute
PropertyPath fidSelectPath = PropertyPathFactory.createPropertyPath( feature.getName() );
fidSelectPath.append( PropertyPathFactory.createPropertyPathStep( propertyName ) );
fidSelectPath.append( PropertyPathFactory.createPropertyPathStep( ( (Feature) propertyValue ).getName() ) );
QualifiedName qn = new QualifiedName( CommonNamespaces.GML_PREFIX, "id",
CommonNamespaces.GMLNS );
fidSelectPath.append( PropertyPathFactory.createAttributePropertyPathStep( qn ) );
// hack that remove's the gml id prefix
MappedFeatureType subFeatureType = (MappedFeatureType) ( (Feature) propertyValue ).getFeatureType();
MappedGMLId gmlId = subFeatureType.getGMLId();
String prefix = gmlId.getPrefix();
if ( subFeatureId.indexOf( prefix ) != 0 ) {
throw new IdGenerationException(
"Internal error: subfeature id '"
+ subFeatureId
+ "' does not begin with the expected prefix." );
}
String plainIdValue = subFeatureId.substring( prefix.length() );
PropertyIsCOMPOperation propertyTestOperation = new PropertyIsCOMPOperation(
OperationDefines.PROPERTYISEQUALTO,
new PropertyName(
fidSelectPath ),
new Literal(
plainIdValue ) );
compOperations.add( propertyTestOperation );
}
} else
LOG.logDebug( "Skipping property '" + propertyName
+ "': not a part of the feature type's identity." );
} else if ( propertyValue instanceof Geometry ) {
if ( propertyType.isIdentityPart() ) {
throw new IdGenerationException(
"Check for equal geometry properties "
+ "is not implemented yet. Do not set "
+ "identityPart to true for geometry properties." );
}
} else {
if ( propertyType.isIdentityPart() ) {
LOG.logDebug( "Need to check for simple property '" + propertyName
+ "' with value '" + propertyValue + "'." );
String value = propertyValue.toString();
if ( propertyValue instanceof Date ) {
value = TimeTools.getISOFormattedTime( (Date) propertyValue );
}
PropertyIsCOMPOperation propertyTestOperation = new PropertyIsCOMPOperation(
OperationDefines.PROPERTYISEQUALTO,
new PropertyName(
propertyName ),
new Literal(
value ) );
compOperations.add( propertyTestOperation );
} else {
LOG.logDebug( "Skipping property '" + propertyName
+ "': not a part of the feature type's identity." );
}
}
}
if ( ft.getGMLId().isIdentityPart() ) {
maybeEqual = false;
LOG.logDebug( "Skipping check for identical features: feature id is part of "
+ "the feature identity." );
}
if ( maybeEqual ) {
// build the filter from the comparison operations
Filter filter = null;
if ( compOperations.size() == 0 ) {
// no constraints, so any feature of this type will do
} else if ( compOperations.size() == 1 ) {
filter = new ComplexFilter( compOperations.get( 0 ) );
} else {
LogicalOperation andOperation = new LogicalOperation( OperationDefines.AND,
compOperations );
filter = new ComplexFilter( andOperation );
}
if ( filter != null ) {
LOG.logDebug( "Performing query with filter: " + filter.toXML() );
} else {
LOG.logDebug( "Performing unrestricted query." );
}
Query query = Query.create( new PropertyPath[0], null, null, null, null,
new QualifiedName[] { feature.getName() }, null, filter, 1,
0, GetFeature.RESULT_TYPE.RESULTS );
try {
FeatureCollection fc = ft.performQuery( query, ta );
if ( fc.size() > 0 ) {
existingFID = fc.getFeature( 0 ).getId();
LOG.logDebug( "Found existing + matching feature with fid: '" + existingFID
+ "'." );
} else {
LOG.logDebug( "No matching feature found." );
}
} catch ( DatastoreException e ) {
throw new IdGenerationException( "Could not perform query to check for "
+ "existing feature instances: " + e.getMessage(),
e );
} catch (UnknownCRSException e) {
e.printStackTrace();
}
}
if ( existingFID != null ) {
LOG.logDebug( "Feature '" + feature.getName() + "', FID '" + feature.getId()
+ "' -> existing FID '" + existingFID + "'" );
feature.setId( existingFID );
this.storedFeatures.add( feature );
this.reassignedFeatures.add( feature );
changeValueForMappedIDProperties( ft, feature );
}
return existingFID;
}
/**
* TODO: remove parentFID hack
* @param feature
* @param parentFID
* @throws IdGenerationException
*/
private void assignNewFIDs( Feature feature, FeatureId parentFID, DatastoreTransaction ta )
throws IdGenerationException {
FeatureId newFid = null;
MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
if ( this.reassignedFeatures.contains( feature ) ) {
LOG.logDebug( "Skipping feature with fid '" + feature.getId()
+ "'. Already reassigned." );
return;
}
this.reassignedFeatures.add( feature );
String oldFidValue = feature.getId();
if ( oldFidValue == null || "".equals( oldFidValue ) ) {
LOG.logDebug( "Feature has no FID. Assigning a new one." );
} else {
newFid = this.oldFid2NewFidMap.get( oldFidValue );
}
if ( newFid == null ) {
// TODO remove these hacks
if ( ft.getGMLId().getIdGenerator() instanceof ParentIDGenerator ) {
newFid = new FeatureId( ft.getGMLId(), parentFID.getValues() );
} else {
newFid = ft.generateFid( ta );
}
this.oldFid2NewFidMap.put( oldFidValue, newFid );
}
LOG.logDebug( "Feature '" + feature.getName() + "', FID '" + oldFidValue + "' -> new FID '"
+ newFid + "'" );
// TODO use FeatureId, not it's String value
feature.setId( newFid.getAsString() );
changeValueForMappedIDProperties( ft, feature );
FeatureProperty[] properties = feature.getProperties();
for ( int i = 0; i < properties.length; i++ ) {
Object propertyValue = properties[i].getValue();
if ( propertyValue instanceof Feature ) {
assignNewFIDs( (Feature) propertyValue, newFid, ta );
}
}
}
/**
* After reassigning a feature id, this method updates all properties of the feature
* that are mapped to the same column as the feature id.
*
* TODO: find a better way to do this
*
* @param ft
* @param feature
*/
private void changeValueForMappedIDProperties( MappedFeatureType ft, Feature feature ) {
// TODO remove this hack as well
String pkColumn = ft.getGMLId().getIdFields()[0].getField();
FeatureProperty[] properties = feature.getProperties();
for ( int i = 0; i < properties.length; i++ ) {
MappedPropertyType propertyType = (MappedPropertyType) ft.getProperty( properties[i].getName() );
if ( propertyType instanceof MappedSimplePropertyType ) {
SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent();
if ( content.isUpdateable() ) {
if ( content instanceof MappingField ) {
String column = ( (MappingField) content ).getField();
if ( column.equalsIgnoreCase( pkColumn ) ) {
Object fid = null;
try {
fid = FeatureId.removeFIDPrefix( feature.getId(), ft.getGMLId() );
} catch ( DatastoreException e ) {
e.printStackTrace();
}
properties[i].setValue( fid );
}
}
}
}
}
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: FeatureIdAssigner.java,v $
Revision 1.31 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.30 2006/09/28 07:49:46 poth
Generics introduced for Filter - LogicalOperation and required adaptions
Revision 1.29 2006/09/26 16:43:22 mschneider
Javadoc corrections + fixed warnings.
Revision 1.28 2006/08/29 15:51:50 mschneider
Changed SimpleContent#isVirtual() to SimpleContent#isUpdateable().
Revision 1.27 2006/08/23 16:31:42 mschneider
Added handling of virtual properties.
Revision 1.26 2006/08/22 18:14:42 mschneider
Refactored due to cleanup of org.deegree.io.datastore.schema package.
Revision 1.25 2006/08/21 15:44:50 mschneider
Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package.
Revision 1.24 2006/07/13 07:27:11 poth
*** empty log message ***
Revision 1.23 2006/07/10 20:14:39 poth
code formatting
Revision 1.22 2006/07/10 08:23:10 poth
code formatation
Revision 1.21 2006/06/07 17:12:46 mschneider
Removed direct call of Query constructor.
Revision 1.20 2006/05/16 16:19:41 mschneider
Refactored due to the splitting of org.deegree.ogcwebservices.wfs.operation package.
Revision 1.19 2006/05/15 10:50:05 mschneider
Lookup of existing features is done in right transaction context now.
Revision 1.18 2006/05/12 15:26:05 poth
*** empty log message ***
Revision 1.17 2006/05/08 07:44:41 poth
*** empty log message ***
Revision 1.16 2006/04/27 09:44:59 poth
*** empty log message ***
Revision 1.15 2006/04/19 18:23:03 mschneider
Fixed problem with identification of existing features when no constraints are given at all (no properties). IdentityPart on the feature id is respected now.
Revision 1.14 2006/04/18 12:44:33 mschneider
Improved javadoc.
Revision 1.13 2006/04/10 16:35:47 mschneider
Added check for geometry properties that have identityPart set to true.
Revision 1.12 2006/04/07 17:12:45 mschneider
Added several hacks to make it work. Needs some serious love, though.
Revision 1.11 2006/04/06 20:25:32 poth
*** empty log message ***
Revision 1.10 2006/04/04 20:39:44 poth
*** empty log message ***
Revision 1.9 2006/04/04 17:50:31 mschneider
Renamed checkForEqualFeature() to checkForStoredFeature().
Revision 1.8 2006/04/04 10:27:19 mschneider
More work on checkForEqualFeature(). Not activated yet.
Revision 1.7 2006/03/30 21:20:29 poth
*** empty log message ***
Revision 1.6 2006/03/29 14:54:43 mschneider
Started to implement check that finds equal feature instances.
Revision 1.5 2006/03/28 13:35:36 mschneider
Changed getNewId() so transaction context (DatastoreTransaction) and thus JDBC connection is accessible in the method.
Revision 1.4 2006/02/24 14:34:46 mschneider
Added hack to handle properties that are mapped to the same column as the feature id.
Revision 1.3 2006/02/23 15:28:36 mschneider
Added ParentIDGenerator.
Revision 1.2 2006/02/04 20:08:54 mschneider
Adapted to use FeatureId class.
Revision 1.1 2006/02/03 18:12:17 mschneider
Initial version.
********************************************************************** */