001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wfs/operation/transaction/TransactionDocument.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 by:
006 Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043 package org.deegree.ogcwebservices.wfs.operation.transaction;
044
045 import java.io.IOException;
046 import java.net.URI;
047 import java.net.URL;
048 import java.util.ArrayList;
049 import java.util.LinkedHashMap;
050 import java.util.List;
051 import java.util.Map;
052
053 import org.deegree.datatypes.QualifiedName;
054 import org.deegree.framework.xml.XMLParsingException;
055 import org.deegree.framework.xml.XMLTools;
056 import org.deegree.i18n.Messages;
057 import org.deegree.model.crs.UnknownCRSException;
058 import org.deegree.model.feature.Feature;
059 import org.deegree.model.feature.FeatureCollection;
060 import org.deegree.model.feature.FeatureFactory;
061 import org.deegree.model.feature.FeatureProperty;
062 import org.deegree.model.feature.GMLFeatureCollectionDocument;
063 import org.deegree.model.feature.GMLFeatureDocument;
064 import org.deegree.model.filterencoding.AbstractFilter;
065 import org.deegree.model.filterencoding.Filter;
066 import org.deegree.ogcbase.CommonNamespaces;
067 import org.deegree.ogcbase.PropertyPath;
068 import org.deegree.ogcwebservices.InvalidParameterValueException;
069 import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequestDocument;
070 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
071 import org.w3c.dom.Element;
072 import org.w3c.dom.Node;
073 import org.w3c.dom.Text;
074 import org.xml.sax.SAXException;
075
076 /**
077 * Parser for "wfs:Transaction" requests and contained elements.
078 *
079 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
080 * @author last edited by: $Author: apoth $
081 *
082 * @version $Revision: 9345 $, $Date: 2007-12-27 17:22:25 +0100 (Do, 27 Dez 2007) $
083 */
084 public class TransactionDocument extends AbstractWFSRequestDocument {
085
086 private static final long serialVersionUID = -394478447170286393L;
087
088 private static final String XML_TEMPLATE = "TransactionTemplate.xml";
089
090 private static final QualifiedName VALUE_ELEMENT_NAME = new QualifiedName( "wfs", "Value", CommonNamespaces.WFSNS );
091
092 /**
093 * Creates a skeleton document that contains the root element and the namespace bindings only.
094 *
095 * @throws IOException
096 * @throws SAXException
097 */
098 public void createEmptyDocument()
099 throws IOException, SAXException {
100 URL url = TransactionDocument.class.getResource( XML_TEMPLATE );
101 if ( url == null ) {
102 throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
103 }
104 load( url );
105 }
106
107 /**
108 * Parses the underlying document into a <code>Transaction</code> request object.
109 *
110 * @param id
111 * @return corresponding <code>Transaction</code> object
112 * @throws XMLParsingException
113 * @throws InvalidParameterValueException
114 */
115 public Transaction parse( String id )
116 throws XMLParsingException, InvalidParameterValueException {
117
118 checkServiceAttribute();
119 String version = checkVersionAttribute();
120
121 Element root = this.getRootElement();
122 String lockId = null;
123 boolean releaseAllFeatures = parseReleaseActionParameter();
124
125 List<TransactionOperation> operations = new ArrayList<TransactionOperation>();
126 List<Element> list = XMLTools.getElements( root, "*", nsContext );
127 for ( int i = 0; i < list.size(); i++ ) {
128 Element element = list.get( i );
129 if ( "LockId".equals( element.getLocalName() )
130 && CommonNamespaces.WFSNS.toString().equals( element.getNamespaceURI() ) ) {
131 lockId = XMLTools.getNodeAsString( element, "text()", nsContext, null );
132 } else {
133 TransactionOperation operation = parseOperation( element );
134 operations.add( operation );
135 }
136 }
137
138 // vendorspecific attributes; required by deegree rights management
139 Map<String, String> vendorSpecificParams = parseDRMParams( root );
140
141 return new Transaction( id, version, vendorSpecificParams, lockId, operations, releaseAllFeatures, this );
142 }
143
144 /**
145 * Parses the optional "releaseAction" attribute of the root element.
146 *
147 * @return true, if releaseAction equals "ALL" (or is left out), false if it equals "SOME"
148 * @throws InvalidParameterValueException
149 * if parameter
150 * @throws XMLParsingException
151 */
152 private boolean parseReleaseActionParameter()
153 throws InvalidParameterValueException, XMLParsingException {
154
155 String releaseAction = XMLTools.getNodeAsString( getRootElement(), "@releaseAction", nsContext, "ALL" );
156 boolean releaseAllFeatures = true;
157 if ( releaseAction != null ) {
158 if ( "SOME".equals( releaseAction ) ) {
159 releaseAllFeatures = false;
160 } else if ( "ALL".equals( releaseAction ) ) {
161 releaseAllFeatures = true;
162 } else {
163 throw new InvalidParameterValueException( "releaseAction", releaseAction );
164 }
165 }
166 return releaseAllFeatures;
167 }
168
169 /**
170 * Parses the given element as a <code>TransactionOperation</code>.
171 * <p>
172 * The given element must be one of the following:
173 * <ul>
174 * <li>wfs:Insert</li>
175 * <li>wfs:Update</li>
176 * <li>wfs:Delete</li>
177 * <li>wfs:Native</li>
178 * </ul>
179 *
180 * @param element
181 * operation element
182 * @return corresponding <code>TransactionOperation</code> object
183 * @throws XMLParsingException
184 */
185 private TransactionOperation parseOperation( Element element )
186 throws XMLParsingException {
187
188 TransactionOperation operation = null;
189
190 if ( !element.getNamespaceURI().equals( CommonNamespaces.WFSNS.toString() ) ) {
191 String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() );
192 throw new XMLParsingException( msg );
193 }
194 if ( element.getLocalName().equals( "Insert" ) ) {
195 operation = parseInsert( element );
196 } else if ( element.getLocalName().equals( "Update" ) ) {
197 operation = parseUpdate( element );
198 } else if ( element.getLocalName().equals( "Delete" ) ) {
199 operation = parseDelete( element );
200 } else if ( element.getLocalName().equals( "Native" ) ) {
201 operation = parseNative( element );
202 } else {
203 String msg = Messages.getMessage( "WFS_INVALID_OPERATION", element.getNodeName() );
204 throw new XMLParsingException( msg );
205 }
206
207 return operation;
208 }
209
210 /**
211 * Parses the given element as a "wfs:Insert" operation.
212 *
213 * @param element
214 * "wfs:Insert" operation
215 * @return corresponding Insert object
216 * @throws XMLParsingException
217 */
218 private Insert parseInsert( Element element )
219 throws XMLParsingException {
220 FeatureCollection fc = null;
221 ID_GEN mode = parseIdGen( element );
222 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
223 URI srsName = XMLTools.getNodeAsURI( element, "@srsName", nsContext, null );
224 List<Element> childElementList = XMLTools.getRequiredElements( element, "*", nsContext );
225
226 // either one _gml:FeatureCollection element or any number of _gml:Feature elements
227 boolean isFeatureCollection = isFeatureCollection( childElementList.get( 0 ) );
228
229 if ( isFeatureCollection ) {
230 LOG.logDebug( "Insert (FeatureCollection)" );
231 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument( false );
232 doc.setRootElement( childElementList.get( 0 ) );
233 doc.setSystemId( this.getSystemId() );
234 fc = doc.parse();
235 } else {
236 LOG.logDebug( "Insert (Features)" );
237 Feature[] features = new Feature[childElementList.size()];
238 for ( int i = 0; i < childElementList.size(); i++ ) {
239 try {
240 GMLFeatureDocument doc = new GMLFeatureDocument( false );
241 doc.setRootElement( childElementList.get( i ) );
242 doc.setSystemId( this.getSystemId() );
243 features[i] = doc.parseFeature();
244 } catch ( Exception e ) {
245 throw new XMLParsingException( e.getMessage(), e );
246 }
247 }
248 fc = FeatureFactory.createFeatureCollection( null, features );
249 }
250
251 return new Insert( handle, mode, srsName, fc );
252 }
253
254 /**
255 * Checks whether the given element is a (concrete) gml:_FeatureCollection element.
256 * <p>
257 * NOTE: This check is far from perfect. Instead of determining the type of the element by inspecting the schema,
258 * the decision is made by checking for child elements with name "gml:featureMember".
259 *
260 * @param element
261 * potential gml:_FeatureCollection element
262 * @return true, if the given element appears to be a gml:_FeatureCollection element, false otherwise
263 * @throws XMLParsingException
264 */
265 private boolean isFeatureCollection( Element element )
266 throws XMLParsingException {
267 boolean containsFeatureCollection = false;
268 List<Node> nodeList = XMLTools.getNodes( element, "gml:featureMember", nsContext );
269 if ( nodeList.size() > 0 ) {
270 containsFeatureCollection = true;
271 }
272 return containsFeatureCollection;
273 }
274
275 /**
276 * Parses the optional "idGen" attribute of the given "wfs:Insert" element.
277 *
278 * @param element
279 * "wfs:Insert" element
280 * @return "idGen" attribute code
281 * @throws XMLParsingException
282 */
283 private ID_GEN parseIdGen( Element element )
284 throws XMLParsingException {
285 ID_GEN mode;
286 String idGen = XMLTools.getNodeAsString( element, "@idgen", nsContext, Insert.ID_GEN_GENERATE_NEW_STRING );
287
288 if ( Insert.ID_GEN_GENERATE_NEW_STRING.equals( idGen ) ) {
289 mode = Insert.ID_GEN.GENERATE_NEW;
290 } else if ( Insert.ID_GEN_USE_EXISTING_STRING.equals( idGen ) ) {
291 mode = Insert.ID_GEN.USE_EXISTING;
292 } else if ( Insert.ID_GEN_REPLACE_DUPLICATE_STRING.equals( idGen ) ) {
293 mode = Insert.ID_GEN.REPLACE_DUPLICATE;
294 } else {
295 String msg = Messages.getMessage( "WFS_INVALID_IDGEN_VALUE", idGen, Insert.ID_GEN_GENERATE_NEW_STRING,
296 Insert.ID_GEN_REPLACE_DUPLICATE_STRING, Insert.ID_GEN_USE_EXISTING_STRING );
297 throw new XMLParsingException( msg );
298 }
299 return mode;
300 }
301
302 /**
303 * Parses the given element as a "wfs:Update" operation.
304 *
305 * @param element
306 * "wfs:Update" operation
307 * @return corresponding Update object
308 * @throws XMLParsingException
309 */
310 private Update parseUpdate( Element element )
311 throws XMLParsingException {
312
313 Update update = null;
314 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
315 QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext );
316
317 Element filterElement = (Element) XMLTools.getNode( element, "ogc:Filter", nsContext );
318 Filter filter = null;
319 if ( filterElement != null ) {
320 filter = AbstractFilter.buildFromDOM( filterElement );
321 }
322
323 List<Node> properties = XMLTools.getNodes( element, "wfs:Property", nsContext );
324 if ( properties.size() > 0 ) {
325 // "standard" update (specifies properties + their replacement values)
326 LOG.logDebug( "Update (replacement properties)" );
327 Map<PropertyPath, FeatureProperty> replacementProps = new LinkedHashMap<PropertyPath, FeatureProperty>();
328 Map<PropertyPath, Node> rawProps = new LinkedHashMap<PropertyPath, Node>();
329 for ( int i = 0; i < properties.size(); i++ ) {
330 Node propNode = properties.get( i );
331 Text propNameNode = (Text) XMLTools.getRequiredNode( propNode, "wfs:Name/text()", nsContext );
332 PropertyPath propPath = parsePropertyPath( propNameNode );
333
334 // TODO remove this (only needed, because Update makes the DOM representation available)
335 Node valueNode = XMLTools.getNode( propNode, "wfs:Value/*", nsContext );
336 if ( valueNode == null ) {
337 valueNode = XMLTools.getNode( propNode, "wfs:Value/text()", nsContext );
338 }
339 rawProps.put( propPath, propNode );
340
341 FeatureProperty replacementProp = null;
342 QualifiedName propName = propPath.getStep( propPath.getSteps() - 1 ).getPropertyName();
343
344 if ( replacementProps.get( propPath ) != null ) {
345 String msg = Messages.getMessage( "WFS_UPDATE_DUPLICATE_PROPERTY", handle, propPath );
346 throw new XMLParsingException( msg );
347 }
348
349 // TODO improve generation of FeatureProperty from "wfs:Value" element
350 Object propValue = null;
351 Node node = XMLTools.getNode( propNode, "wfs:Value", nsContext );
352 if ( node != null ) {
353 GMLFeatureDocument dummyDoc = new GMLFeatureDocument( false );
354 dummyDoc.setRootElement( (Element) propNode );
355 dummyDoc.setSystemId( this.getSystemId() );
356 Feature dummyFeature;
357 try {
358 dummyFeature = dummyDoc.parseFeature();
359 } catch ( UnknownCRSException e ) {
360 throw new XMLParsingException( e.getMessage(), e );
361 }
362 propValue = dummyFeature.getProperties( VALUE_ELEMENT_NAME )[0].getValue();
363 }
364 replacementProp = FeatureFactory.createFeatureProperty( propName, propValue );
365 replacementProps.put( propPath, replacementProp );
366 }
367 update = new Update( handle, typeName, replacementProps, rawProps, filter );
368 } else {
369 // deegree specific update (specifies a single replacement feature)
370 LOG.logDebug( "Update (replacement feature)" );
371 Feature replacementFeature = null;
372 List<Element> childElementList = XMLTools.getRequiredElements( element, "*", nsContext );
373 if ( ( filter == null && childElementList.size() != 1 )
374 || ( filter != null && childElementList.size() != 2 ) ) {
375 String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle );
376 throw new XMLParsingException( msg );
377 }
378 try {
379 GMLFeatureDocument doc = new GMLFeatureDocument( false );
380 doc.setRootElement( childElementList.get( 0 ) );
381 doc.setSystemId( this.getSystemId() );
382 replacementFeature = doc.parseFeature();
383 } catch ( Exception e ) {
384 String msg = Messages.getMessage( "WFS_UPDATE_FEATURE_REPLACE", handle );
385 throw new XMLParsingException( msg );
386 }
387 update = new Update( handle, typeName, replacementFeature, filter );
388 }
389
390 return update;
391 }
392
393 /**
394 * Parses the given element as a "wfs:Delete" operation.
395 *
396 * @param element
397 * "wfs:Delete" operation
398 * @return corresponding Delete object
399 */
400 private Delete parseDelete( Element element )
401 throws XMLParsingException {
402
403 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
404 QualifiedName typeName = XMLTools.getRequiredNodeAsQualifiedName( element, "@typeName", nsContext );
405
406 Element filterElement = (Element) XMLTools.getRequiredNode( element, "ogc:Filter", nsContext );
407 Filter filter = AbstractFilter.buildFromDOM( filterElement );
408 return new Delete( handle, typeName, filter );
409 }
410
411 /**
412 * Parses the given element as a "wfs:Native" operation.
413 *
414 * @param element
415 * "wfs:Native" operation
416 * @return corresponding Native object
417 */
418 private Native parseNative( Element element )
419 throws XMLParsingException {
420 String handle = XMLTools.getNodeAsString( element, "@handle", nsContext, null );
421 String vendorID = XMLTools.getRequiredNodeAsString( element, "@vendorId", nsContext );
422 boolean safeToIgnore = XMLTools.getRequiredNodeAsBoolean( element, "@safeToIgnore", nsContext );
423 return new Native( handle, element, vendorID, safeToIgnore );
424 }
425 }