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