Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance WFS-T by being able to insert features that reference features which are already inserted #820

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ private static Pair<Object, Object> getPrimitives( Object value1, Object value2

/**
* Determines all {@link Feature} and {@link Geometry} objects contained in the given {@link TypedObjectNode} and
* their ids.
* their ids. Does <code>not</code> include internal referenced {@link Feature}s.
*
* @param node
* typed object node to be scanned, can be <code>null</code>
Expand All @@ -217,11 +217,15 @@ public static void findFeaturesAndGeometries( TypedObjectNode node, Set<Geometry
} else if ( node instanceof org.deegree.commons.tom.Object ) {
if ( node instanceof Reference<?> ) {
Reference<?> ref = (Reference<?>) node;
if ( ref.isResolved() ) {
node = ( (Reference<?>) node ).getReferencedObject();
} else if ( node instanceof Reference<?> ) {
if ( ref.isResolved() && !ref.isInternalResolved() ) {
node = ref.getReferencedObject();
} else {
try {
node = ( (Reference<?>) node ).getReferencedObject();
TypedObjectNode referencedObject = ref.getReferencedObject();
if ( !ref.isInternalResolved() )
node = referencedObject;
else
return;
} catch ( ReferenceResolvingException e ) {
LOG.warn( "Unable to resolve external reference '" + ref.getURI() + ". Ignoring." );
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ public class GMLStreamReader {

private boolean laxMode;

private GMLReferenceResolver internalResolver;

/**
* Creates a new {@link GMLStreamReader} instance.
*
Expand Down Expand Up @@ -251,6 +253,14 @@ public void setResolver( GMLReferenceResolver resolver ) {
this.resolver = resolver;
}

public void setInternalResolver ( GMLReferenceResolver internalResolver ) {
this.internalResolver = internalResolver;
}

public GMLReferenceResolver getInternalResolver () {
return internalResolver;
}

/**
* Enables or disables lax parsing (disable syntactical checks).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ public abstract class AbstractGMLObjectReader extends XMLAdapter {

protected static final QName XSI_NIL = new QName( XSINS, "nil", "xsi" );

private final GMLReferenceResolver internalResolver;

// TODO should be final, but is currently modified by GMLFeatureReader
protected AppSchema schema;

Expand All @@ -167,6 +169,7 @@ public abstract class AbstractGMLObjectReader extends XMLAdapter {
protected AbstractGMLObjectReader( GMLStreamReader gmlStreamReader ) {
this.gmlStreamReader = gmlStreamReader;
this.specialResolver = gmlStreamReader.getResolver();
this.internalResolver = gmlStreamReader.getInternalResolver();
this.idContext = gmlStreamReader.getIdContext();
// TODO
this.schema = gmlStreamReader.getAppSchema();
Expand Down Expand Up @@ -322,9 +325,17 @@ private Property parseFeatureProperty( XMLStreamReaderWrapper xmlStream, Feature
if ( href != null ) {
FeatureReference refFeature = null;
if ( specialResolver != null ) {
refFeature = new FeatureReference( specialResolver, href, xmlStream.getSystemId() );
if( internalResolver == null ) {
refFeature = new FeatureReference( specialResolver, href, xmlStream.getSystemId() );
} else {
refFeature = new FeatureReference( specialResolver, internalResolver, href, xmlStream.getSystemId() );
}
} else {
if( internalResolver == null ) {
refFeature = new FeatureReference( idContext, href, xmlStream.getSystemId() );
} else {
refFeature = new FeatureReference( idContext, internalResolver, href, xmlStream.getSystemId() );
}
}
idContext.addReference( refFeature );
List<TypedObjectNode> values = new ArrayList<TypedObjectNode>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@

import javax.xml.namespace.QName;

import org.deegree.commons.tom.ReferenceResolvingException;
import org.deegree.commons.tom.TypedObjectNode;
import org.deegree.commons.tom.gml.GMLObject;
import org.deegree.commons.tom.gml.GMLReference;
import org.deegree.commons.tom.gml.GMLReferenceResolver;
import org.deegree.commons.tom.gml.property.Property;
import org.deegree.feature.Feature;
import org.deegree.feature.property.ExtraProps;
import org.deegree.feature.types.FeatureType;
import org.deegree.geometry.Envelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A {@link GMLReference} that targets a {@link Feature}.
Expand All @@ -59,6 +63,12 @@
*/
public class FeatureReference extends GMLReference<Feature> implements Feature {

private static final Logger LOG = LoggerFactory.getLogger( FeatureReference.class );

private final GMLReferenceResolver internalResolver;

private boolean internalResolved = false;

/**
* Creates a new {@link FeatureReference} instance.
*
Expand All @@ -70,7 +80,24 @@ public class FeatureReference extends GMLReference<Feature> implements Feature {
* base URL for resolving the uri, may be <code>null</code> (no resolving of relative URLs)
*/
public FeatureReference( GMLReferenceResolver resolver, String uri, String baseURL ) {
this( resolver, null, uri, baseURL );
}

/**
* Creates a new {@link FeatureReference} instance.
*
* @param resolver
* used for resolving the reference, must not be <code>null</code>
* @param internalResolver
* used for resolving references, may be <code>null</code>
* @param uri
* the feature's uri, must not be <code>null</code>
* @param baseURL
* base URL for resolving the uri, may be <code>null</code> (no resolving of relative URLs)
*/
public FeatureReference( GMLReferenceResolver resolver, GMLReferenceResolver internalResolver, String uri, String baseURL ) {
super( resolver, uri, baseURL );
this.internalResolver = internalResolver;
}

@Override
Expand Down Expand Up @@ -139,4 +166,38 @@ public void setExtraProperties( ExtraProps extraProps ) {
getReferencedObject().setExtraProperties( extraProps );
}

}
@Override
public synchronized Feature getReferencedObject()
throws ReferenceResolvingException {
try {
return super.getReferencedObject();
} catch ( ReferenceResolvingException e ) {
if ( internalResolver == null )
throw e;
return resolveInternalFeature( e );
}
}

@Override
public boolean isInternalResolved() {
return internalResolved;
}

private Feature resolveInternalFeature( ReferenceResolvingException e ) {
String uri = getURI();
GMLObject object = this.internalResolver.getObject( uri, getBaseURL() );
if ( object != null ) {
if ( object instanceof Feature ) {
LOG.info( "Feature with uri {} could be resolved by the internal resolver.", uri );
resolve( (Feature) object );
this.internalResolved = true;
return (Feature) object;
}
String msg = "Object with uri '" + uri
+ "' could be resolved from internal resolver but is no Feature instance.";
throw exception = new ReferenceResolvingException( msg );
}
throw e;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class Reference<T extends Object> implements Object {

private T object;

private ReferenceResolvingException exception;
protected ReferenceResolvingException exception;

/**
* Creates a new {@link Reference} instance.
Expand Down Expand Up @@ -106,11 +106,30 @@ public boolean isResolved() {
return object != null;
}

/**
* Returns whether the reference has been resolved and is an internal reference.
*
* @return <code>true</code> if the reference is resolved is an internal reference, <code>false</code> if the
* reference has not been resolved or is not internal
*/
public boolean isInternalResolved() {
return false;
}

// TODO can we get rid of this method?
public boolean isLocal() {
return uri.startsWith( "#" );
}

/**
* Returns the base URL for resolving the uri.
*
* @return base URL for resolving the uri, may be <code>null</code> (no resolving of relative URLs)
*/
public String getBaseURL() {
return baseURL;
}

/**
* Sets the referenced object.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,13 @@ public void buildInsertRows( final TypedObjectNode particle, final Mapping mappi
String href = null;
Feature feature = (Feature) getPropValue( value );
if ( feature instanceof FeatureReference ) {
if ( ( (FeatureReference) feature ).isLocal() || ( (FeatureReference) feature ).isResolved() ) {
FeatureReference featureReference = (FeatureReference) feature;
if ( ( featureReference.isLocal() || featureReference.isResolved() )
&& !featureReference.isInternalResolved() ) {
subFeatureRow = lookupFeatureRow( feature.getId() );
}
// always use the uri if href is mapped explicitly
href = ( (FeatureReference) feature ).getURI();
href = featureReference.getURI();
MappingExpression me = ( (FeatureMapping) mapping ).getHrefMapping();
if ( !( me instanceof DBField ) ) {
LOG.debug( "Skipping feature mapping (href). Not mapped to database column." );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import org.deegree.feature.GenericFeatureCollection;
import org.deegree.feature.persistence.FeatureStore;
import org.deegree.feature.persistence.FeatureStoreException;
import org.deegree.feature.persistence.FeatureStoreGMLIdResolver;
import org.deegree.feature.persistence.FeatureStoreTransaction;
import org.deegree.feature.persistence.lock.Lock;
import org.deegree.feature.persistence.lock.LockManager;
Expand Down Expand Up @@ -171,24 +172,27 @@ class TransactionHandler {

private final IDGenMode idGenMode;

private final boolean allowFeatureReferencesToDatastore;

/**
* Creates a new {@link TransactionHandler} instance that uses the given service to lookup requested
* {@link FeatureType}s.
*
* @param master
*
* @param master
*
* @param service
* WFS instance used to lookup the feature types
* @param request
* request to be handled
* request to be handled
* @param idGenMode
* @param allowFeatureReferencesToDatastore
*/
TransactionHandler( WebFeatureService master, WfsFeatureStoreManager service, Transaction request,
IDGenMode idGenMode ) {
IDGenMode idGenMode, boolean allowFeatureReferencesToDatastore ) {
this.master = master;
this.service = service;
this.request = request;
this.idGenMode = idGenMode;
this.allowFeatureReferencesToDatastore = allowFeatureReferencesToDatastore;
}

/**
Expand Down Expand Up @@ -434,8 +438,11 @@ private FeatureCollection parseFeaturesOrCollection( XMLStreamReader xmlStream,
FeatureCollection fc = null;

// TODO determine correct schema
AppSchema schema = service.getStores()[0].getSchema();
FeatureStore featureStore = service.getStores()[0];
AppSchema schema = featureStore.getSchema();
GMLStreamReader gmlStream = GMLInputFactory.createGMLStreamReader( inputFormat, xmlStream );
if ( allowFeatureReferencesToDatastore )
gmlStream.setInternalResolver( new FeatureStoreGMLIdResolver( featureStore ) );
gmlStream.setApplicationSchema( schema );
gmlStream.setDefaultCRS( defaultCRS );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ public class WebFeatureService extends AbstractOWS {

private boolean enableResponsePaging;

private boolean allowFeatureReferencesToDatastore = false;

private OWSMetadataProvider mdProvider;

public WebFeatureService( ResourceMetadata<OWS> metadata, Workspace workspace, Object jaxbConfig ) {
Expand All @@ -275,6 +277,7 @@ public void init( DeegreeServicesMetadataType serviceMetadata, DeegreeServiceCon
if ( enableTransactions != null ) {
this.enableTransactions = enableTransactions.isValue();
this.idGenMode = parseIdGenMode( enableTransactions.getIdGen() );
this.allowFeatureReferencesToDatastore = enableTransactions.isAllowFeatureReferencesToDatastore();
}
if ( jaxbConfig.isEnableResponseBuffering() != null ) {
disableBuffering = !jaxbConfig.isEnableResponseBuffering();
Expand Down Expand Up @@ -793,7 +796,7 @@ public void doKVP( Map<String, String> kvpParamsUC, HttpServletRequest request,
}
checkTransactionsEnabled( requestName );
Transaction transaction = TransactionKVPAdapter.parse( kvpParamsUC );
new TransactionHandler( this, service, transaction, idGenMode ).doTransaction( response );
new TransactionHandler( this, service, transaction, idGenMode, allowFeatureReferencesToDatastore ).doTransaction( response );
break;
default:
throw new RuntimeException( "Internal error: Unhandled request '" + requestName + "'." );
Expand Down Expand Up @@ -956,7 +959,7 @@ public void doXML( XMLStreamReader xmlStream, HttpServletRequest request, HttpRe
checkTransactionsEnabled( requestName );
TransactionXmlReader transactionReader = new TransactionXmlReaderFactory().createReader( xmlStream );
Transaction transaction = transactionReader.read( xmlStream );
new TransactionHandler( this, service, transaction, idGenMode ).doTransaction( response );
new TransactionHandler( this, service, transaction, idGenMode, allowFeatureReferencesToDatastore ).doTransaction( response );
break;
default:
throw new RuntimeException( "Internal error: Unhandled request '" + requestName + "'." );
Expand Down Expand Up @@ -1123,7 +1126,7 @@ public void doSOAP( SOAPEnvelope soapDoc, HttpServletRequest request, HttpRespon
checkTransactionsEnabled( requestName );
TransactionXmlReader transactionReader = new TransactionXmlReaderFactory().createReader( requestVersion );
Transaction transaction = transactionReader.read( bodyXmlStream );
new TransactionHandler( this, service, transaction, idGenMode ).doTransaction( response );
new TransactionHandler( this, service, transaction, idGenMode, allowFeatureReferencesToDatastore ).doTransaction( response );
break;
default:
throw new RuntimeException( "Internal error: Unhandled request '" + requestName + "'." );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<simpleContent>
<extension base="boolean">
<attribute name="idGen" type="wfs:IdentifierGenerationOptionType" use="optional" default="GenerateNew" />
<attribute name="allowFeatureReferencesToDatastore" type="boolean" use="optional" default="false" />
</extension>
</simpleContent>
</complexType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ General options
Transactions
^^^^^^^^^^^^

By default, WFS-T requests will be rejected. Setting the ``EnableTransactions`` option to ``true`` will enable transaction support. This option has the optional attribute ``idGenMode`` which controls how ids of inserted features (the values in the gml:id attribute) are treated. There are three id generation modes available:
By default, WFS-T requests will be rejected. Setting the ``EnableTransactions`` option to ``true`` will enable transaction support. This option has two optional attributes: ``allowFeatureReferencesToDatastore`` and ``idGenMode``. If ``allowFeatureReferencesToDatastore`` is true it is allowed to insert features with references to already inserted features, default is false. ``idGenMode`` controls how ids of inserted features (the values in the gml:id attribute) are treated. There are three id generation modes available:

* **UseExisting**: The original gml:id values from the input are stored. This may lead to errors if the provided ids are already in use.
* **GenerateNew** (default): New and unique ids are generated. References in the input GML (xlink:href) that point to a feature with an reassigned id are fixed as well, so reference consistency is maintained.
Expand Down