Configuration of a Transactional WFS
If you like to enable transactions with your WFS, you have to adapt the configuration of your featuretypes.
Contents
1. Requirements
Using a datastore based on a database (Transactions on Shapefiles do not work!)
- Supported DBs: PostgreSQL/PostGIS, Oracle
2. Configuration
2.1. Enabling a non-transactional WFS to be transactional
Per default a deegree2 WFS is non-transactional. You can enable it if you set the transaction attributes to true:
<deegreewfs:transaction update="true" delete="true" insert="true"/>
- A first example: ID Generation based on UUIDs
<xsd:element name="springs" type="app:springsType" substitutionGroup="gml:_Feature"> <xsd:annotation> <xsd:appinfo> <deegreewfs:table>springs</deegreewfs:table> <deegreewfs:gmlId prefix="ID_"> <deegreewfs:MappingField field="UUID" type="VARCHAR"/> <deegreewfs:IdGenerator type="UUID"/> </deegreewfs:gmlId> <deegreewfs:transaction update="true" delete="true" insert="true"/> </xsd:appinfo> </xsd:annotation> </xsd:element>
- ID Generation based on a DB Sequence
<xsd:element name="springs" type="app:springsType" substitutionGroup="gml:_Feature"> <xsd:annotation> <xsd:appinfo> <deegreewfs:table>springs</deegreewfs:table> <deegreewfs:gmlId prefix="ID_"> <deegreewfs:IdGenerator type="DB_SEQ"/> <deegreewfs:param name="sequence">OBJECT_ID_seq</deegreewfs:param> </deegreewfs:gmlId> <deegreewfs:transaction update="true" delete="true" insert="true"/> </xsd:appinfo> </xsd:annotation> </xsd:element>
3. Defining WFS-T feature equality / what the heck are these 'deegreewfs:!IdentityPart' elements?
Deegree offers a mechanism to prevent the insertion of multiple instances of equal features, i.e. a possibility to use property-equivalence additional to gml:id-equivalence as a means of deciding if a new feature has to be inserted (or is just an instance of a stored feature).
3.1. Facts
- Only relevant for 'insert' and 'update' operations inside a transaction request.
- Defines the equals relation between features of the same type.
- Prevents database redundancies.
- Simple properties as well as complex properties can be used as "identityPart"s. Currently, geometries are ignored as identityPart, because it's not as clear to decide when two geometries are equal (rounding errors, sequence of points,...).
- NOTE: gml:id equality has highest priority: if a feature is to be inserted and a feature with the same gml:id already exists in the datastore, it is not inserted again (despite any differences in the content).
3.2. Example
Consider the following example. It is assumed that our WFS-T allready contains the following feature.
<Employee gml:id="EMP_01">
<name>Jon Arbuckle</name>
<position>Manager</position>
<inRoom>
<Room gml:id="ROOM_01">
<number>313</number>
<numberOfDesks>5</numberOfDesks>
</Room>
</inRoom>
</Employee>
Now, a new employee joins the company, this means following Insert is to be performed (notice the equality of the room numbers):
NOTE: no gml:id is given, because the insert opertation uses idgen="generateNew".
<Employee>
<name>Garfield</name>
<position>Manager's pet</position>
<inRoom>
<Room>
<number>313</number>
<Room>
</inRoom>
</Employee>
After this insert it is expected that only the new employee is inserted and the room (because of the equality of the room number) is only present once. A GetFeature request to this WFS will have following result:
NOTE, the <inRoom> property of the Garfield feature holds an xlink:href to gml:id of the contents of the <inRoom> property of the 'John Arbuckle' feature.
<Employee gml:id="EMP_01">
<name>Jon Arbuckle</name>
<position>Manager</position>
<inRoom>
<Room gml:id="ROOM_01">
<number>313</number>
<numberOfDesks>5</numberOfDesks>
</Room>
</inRoom>
</Employee>
<Employee gml:id="EMP_02">
<name>Garfield</name>
<position>Manager's pet</position>
<inRoom xlink:href="ROOM_01"/>
</Employee>
So we managed to store the "Garfield" feature without storing the <Room> feature twice. Hence we identified the already stored <Room> feature by means of the <number> property instead of the gml:id attribute of the <Room> feature.
Here's the corresponding annotated schema. Notice the "deegreewfs:IdentityPart" elements.
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:deegreewfs="http://www.deegree.org/wfs" xmlns:dgjdbc="http://www.deegree.org/jdbc" xmlns:app="http://www.deegree.org/app" targetNamespace="http://www.deegree.org/app" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/feature.xsd"/>
<xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/geometryAggregates.xsd"/>
<!-- configuration of the persistence backend to be used -->
<xsd:annotation>
<xsd:appinfo>
...
</xsd:appinfo>
</xsd:annotation>
<!-- ====================================================================== -->
<xsd:element name="Employee" type="app:EmployeeType" substitutionGroup="gml:_Feature">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:table>EMPLOYEE</deegreewfs:table>
<deegreewfs:gmlId prefix="EMP_">
<deegreewfs:MappingField field="ID" type="INTEGER"/>
<deegreewfs:IdGenerator type="DB_SEQ">
<deegreewfs:param name="sequence">FID_seq</deegreewfs:param>
</deegreewfs:IdGenerator>
<deegreewfs:IdentityPart>true</deegreewfs:IdentityPart>
</deegreewfs:gmlId>
<deegreewfs:visible>true</deegreewfs:visible>
<deegreewfs:transaction update="true" delete="true" insert="true"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- ====================================================================== -->
<xsd:complexType name="EmployeeType">
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<!-- simple (string) valued property 'name' -->
<xsd:element name="name" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
<deegreewfs:Content>
<deegreewfs:MappingField field="NAME" type="VARCHAR"/>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- simple (string) valued property 'position' -->
<xsd:element name="position" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
<deegreewfs:Content>
<deegreewfs:MappingField field="POSITION" type="VARCHAR"/>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- complex valued property 'inRoom' -->
<xsd:element name="inRoom" type="gml:FeaturePropertyType">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
<deegreewfs:Content type="app:Room">
<deegreewfs:Relation>
<deegreewfs:From fk="true">
<deegreewfs:MappingField field="IN_ROOM" type="INTEGER"/>
</deegreewfs:From>
<deegreewfs:To>
<deegreewfs:MappingField field="ID" type="INTEGER"/>
</deegreewfs:To>
</deegreewfs:Relation>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- ====================================================================== -->
<xsd:element name="Room" type="app:RoomType" substitutionGroup="gml:_Feature">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:table>ROOM</deegreewfs:table>
<deegreewfs:gmlId prefix="ROOM_">
<deegreewfs:MappingField field="ID" type="INTEGER"/>
<deegreewfs:IdGenerator type="DB_SEQ">
<deegreewfs:param name="sequence">FID_seq</deegreewfs:param>
</deegreewfs:IdGenerator>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
</deegreewfs:gmlId>
<deegreewfs:visible>true</deegreewfs:visible>
<deegreewfs:transaction update="true" delete="true" insert="true"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- ====================================================================== -->
<xsd:complexType name="RoomType">
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<!-- simple (string) valued property 'name' -->
<xsd:element name="number" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>true</deegreewfs:IdentityPart>
<deegreewfs:Content>
<deegreewfs:MappingField field="NUMBER" type="VARCHAR"/>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- simple (integer) valued property 'numberOfDesks' -->
<xsd:element name="numberOfDesks" type="xsd:integer">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
<deegreewfs:Content>
<deegreewfs:MappingField field="NUMBER_OF_DESKS" type="INTEGER"/>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- ====================================================================== -->
</xsd:schema>
3.3. How are the deegreewfs:IdentityPart elements used?
You can use deegreewfs:IdentityPart inside deegreewfs:gmlId-elements and in the annotation of of property elements:
<deegreewfs:gmlId prefix="EMP_">
<deegreewfs:MappingField field="ID" type="INTEGER"/>
<deegreewfs:IdGenerator type="DB_SEQ">
<deegreewfs:param name="sequence">FID_seq</deegreewfs:param>
</deegreewfs:IdGenerator>
<deegreewfs:IdentityPart>true</deegreewfs:IdentityPart>
</deegreewfs:gmlId>
<xsd:element name="name" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<deegreewfs:IdentityPart>false</deegreewfs:IdentityPart>
<deegreewfs:Content>
<deegreewfs:MappingField field="NAME" type="VARCHAR"/>
</deegreewfs:Content>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
A deegreewfs:IdentityPart element must either contain the string value "false" or "true".
If you want features to be equal only if their gml:Id matches, set deegreewfs:IdentityPart to "true" inside its gmlId element. Set it to "false" for all properties of this feature type.
If you want features to be equal based on property equality, set deegreewfs:IdentityPart to "false" inside its gmlId element. Set it to "true" for all properties of this feature type that need to be equal in order for the features to be equal.
If you set IdentityPart to true for the gmlId element and for any property of the feature, this is considered to be an error.
NOTE: If you want to use this mechanism, don't omit the deegreewfs:IdentityPart element anywhere (although this is possible). Just put it in every property and gmlId definition. This way you know how the WFS behaves (the defaulting behaviour may be confusing). Always set deegreewfs:IdentityPart to "false" for geometry properties.
4. Set foreign key information
Some best practices and heuristics to set the foreign key attributes in WFS schema documents. This is only necessary for transactional WFS.
4.1. Why foreign key information is needed
- Table rows cannot be inserted / deleted in arbitrary order when foreign key constraints are set on the database (SQL) level. The fk attribute tells the WFS that rows in the table with fk="true" must be deleted before the other side -- otherwise, a foreign key constraint violation would occur in the SQL database.
- Despite foreign key constraints, the WFS must propagate column values in relations: e.g. if a feature property stored in feature table A references feature table B (where the property is stored), inserted rows for table A must derive the value for the referencing column.
4.2. How to set the foreign key information
If foreign key constraints are set on the SQL database, fk="true" must always be set on the foreign key side ("TABLE1.A REFERENCES TA BLE2.B" -> TABLE1.A is the foreign key).
- If the "To"-element of a relation contains the feature id field of the sub feature type, the fk="true" has to be set in the "From"-element.
- Ask yourself: "Which side defines the value and which side should it be propagated to?" The side where it is propagated to gets the fk="true".