001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/portal/standard/wfs/control/DigitizeListener.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
037 package org.deegree.portal.standard.wfs.control;
038
039 import java.io.BufferedReader;
040 import java.io.File;
041 import java.io.FileReader;
042 import java.io.IOException;
043 import java.io.InputStream;
044 import java.io.InputStreamReader;
045 import java.net.URI;
046 import java.net.URISyntaxException;
047 import java.net.URL;
048
049 import javax.servlet.http.HttpServletRequest;
050 import javax.servlet.http.HttpSession;
051
052 import org.apache.commons.httpclient.HttpClient;
053 import org.apache.commons.httpclient.methods.PostMethod;
054 import org.apache.commons.httpclient.methods.StringRequestEntity;
055 import org.deegree.datatypes.QualifiedName;
056 import org.deegree.enterprise.WebUtils;
057 import org.deegree.enterprise.control.AbstractListener;
058 import org.deegree.enterprise.control.FormEvent;
059 import org.deegree.enterprise.control.RPCException;
060 import org.deegree.enterprise.control.RPCMember;
061 import org.deegree.enterprise.control.RPCParameter;
062 import org.deegree.enterprise.control.RPCStruct;
063 import org.deegree.enterprise.control.RPCWebEvent;
064 import org.deegree.framework.log.ILogger;
065 import org.deegree.framework.log.LoggerFactory;
066 import org.deegree.framework.util.CharsetUtils;
067 import org.deegree.framework.util.StringTools;
068 import org.deegree.framework.xml.XMLFragment;
069 import org.deegree.i18n.Messages;
070 import org.deegree.model.crs.CRSFactory;
071 import org.deegree.model.crs.CoordinateSystem;
072 import org.deegree.model.crs.UnknownCRSException;
073 import org.deegree.model.spatialschema.Curve;
074 import org.deegree.model.spatialschema.Geometry;
075 import org.deegree.model.spatialschema.GeometryException;
076 import org.deegree.model.spatialschema.Point;
077 import org.deegree.model.spatialschema.Surface;
078 import org.deegree.model.spatialschema.WKTAdapter;
079 import org.deegree.portal.Constants;
080 import org.deegree.portal.standard.wfs.WFSClientException;
081 import org.deegree.portal.standard.wfs.configuration.DigitizerClientConfiguration;
082 import org.w3c.dom.Element;
083 import org.w3c.dom.NodeList;
084
085 /**
086 * This Listener is used to perform a wfs transaction (e.g. write a geometry that was digitized in
087 * the client to a WFS). The featureType must be passed as first parameter of the RPC request, the
088 * second parameter must contain a struct, whose members are replaced in the transaction template.
089 *
090 * WFS address and transaction template are both taken from the module configuration.
091 *
092 * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
093 * @author last edited by: $Author: mschneider $
094 *
095 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
096 */
097 public class DigitizeListener extends AbstractListener {
098
099 private static final ILogger LOG = LoggerFactory.getLogger( DigitizeListener.class );
100
101 protected static final String DIGITIZER_CLIENT_CONFIGURATION = "DIGITIZER_CLIENT_CONFIGURATION";
102
103 protected static final String GEOMETRY = "GEOMETRY";
104
105 protected static final String CRS = "CRS";
106
107 protected DigitizerClientConfiguration config = null;
108
109 /*
110 * (non-Javadoc)
111 *
112 * @see org.deegree.enterprise.control.WebListener#actionPerformed(org.deegree.enterprise.control.FormEvent)
113 */
114 @Override
115 public void actionPerformed( FormEvent event ) {
116
117 // GET MODULE CONFIGURATION
118 HttpSession session = ( (HttpServletRequest) this.getRequest() ).getSession( true );
119 config = (DigitizerClientConfiguration) session.getAttribute( DIGITIZER_CLIENT_CONFIGURATION );
120
121 // VALIDATE RPC REQUEST
122 RPCWebEvent rpcEvent = (RPCWebEvent) event;
123 try {
124 validateRequest( rpcEvent );
125 } catch ( Exception e ) {
126 LOG.logError( e.getLocalizedMessage() );
127 gotoErrorPage( Messages.getMessage( "IGEO_STD_INVALID_RPC", e.getLocalizedMessage() ) );
128 return;
129 }
130
131 // GET WFS ADDRESS FOR QUERY
132 String address = null;
133 QualifiedName qualiName = null;
134 try {
135 qualiName = extractFeatureTypeAsQualifiedName( rpcEvent );
136 } catch ( Exception e ) {
137 LOG.logError( e.getLocalizedMessage() );
138 gotoErrorPage( Messages.getMessage( "IGEO_STD_INVALID_RPC", e.getLocalizedMessage() ) );
139 return;
140 }
141 address = config.getFeatureTypeAddress( qualiName );
142
143 // CREATE WFS REQUEST
144 String query = null;
145 try {
146 query = createWFSTransactionRequest( rpcEvent, "INSERT" );
147 // special replacements handled individually - only needed in derived Listeners
148 // query = replaceSpecialTemplateVars( query, rpcEvent );
149 } catch ( Exception e ) {
150 LOG.logError( e.getLocalizedMessage() );
151 gotoErrorPage( Messages.getMessage( "IGEO_STD_WFS_CREATE_REQ_FAILED", e.getLocalizedMessage() ) );
152 return;
153 }
154
155 if( LOG.getLevel() == ILogger.LOG_DEBUG ){
156 String msg = StringTools.concat( 300, "WFSTransactionRequest = ", query );
157 LOG.logDebug( msg );
158 }
159
160 // PERFORM QUERY (WFS TRANSACTION)
161 XMLFragment response = null;
162 try {
163 response = performRequest( query, address );
164 } catch ( Exception e ) {
165 LOG.logError( e.getLocalizedMessage() );
166 gotoErrorPage( Messages.getMessage( "IGEO_STD_PERFORM_REQ_FAILED", query, e.getLocalizedMessage() ) );
167 return;
168 }
169
170 if( LOG.getLevel() == ILogger.LOG_DEBUG ){
171 String msg = StringTools.concat( 300, "WFSTransactionResponse = ", response.getAsPrettyString() );
172 LOG.logDebug( msg );
173 }
174
175 // HANLDE RESPONSE
176 Object[] obj = null;
177 try {
178 obj = handleResponse( response );
179 if ( obj != null && obj.length > 0 ) { /* handle obj in derived listeners */
180 }
181 } catch ( Exception e ) {
182 LOG.logError( e.getLocalizedMessage() );
183 gotoErrorPage( Messages.getMessage( "IGEO_STD_WFS_RESPONSE_ERROR", e.getLocalizedMessage() ) );
184 return;
185 }
186
187 }
188
189 /**
190 * Validate the RPC request: number of RPCParameter must be 2 or more.
191 *
192 * The first param must contain the "FEATURE_TYPE". The second param must contain a struct with
193 * all the parameters that shall be replaced in the WFS request template.
194 *
195 * If the struct contains a member "GEOM", then one more member is mandatory: "CRS".
196 *
197 * Further params will be ignored in this Listener. They might want to be used to handle
198 * specific behaviour in derived Listeners.
199 *
200 * @param rpcEvent
201 * @throws RPCException
202 * @throws GeometryException
203 * @throws UnknownCRSException
204 */
205 protected void validateRequest( RPCWebEvent rpcEvent )
206 throws RPCException, GeometryException, UnknownCRSException {
207
208 RPCParameter[] params = rpcEvent.getRPCMethodCall().getParameters();
209
210 // validity check for number of parameters in RPCMethodCall
211 if ( params.length < 2 ) {
212 throw new RPCException( Messages.getMessage( "IGEO_STD_WFS_WRONG_RPC_PARAMS_NUM", "2" ) );
213 }
214
215 String featureType = (String) params[0].getValue();
216 if ( featureType == null || "".equals( featureType ) ) {
217 throw new RPCException( Messages.getMessage( "IGEO_STD_MISSING_RPC_PARAM", "featureType", "1." ) );
218 }
219
220 RPCStruct rpcStruct = (RPCStruct) params[1].getValue();
221
222 // check geometry-string represents a valid GEOMETRY-object
223 RPCMember mem = rpcStruct.getMember( GEOMETRY );
224
225 if ( mem != null ) {
226 String geom = (String) mem.getValue();
227 RPCMember crsMem = rpcStruct.getMember( CRS );
228 if ( crsMem == null ) {
229 throw new RPCException(
230 Messages.getMessage( "IGEO_STD_MISSING_RPC_PARAM_DEPENDENCY", "GEOMETRY", "CRS" ) );
231 }
232 String crsName = (String) crsMem.getValue();
233 CoordinateSystem crs = CRSFactory.create( crsName );
234
235 Geometry g = WKTAdapter.wrap( geom, crs );
236
237 if ( !( g instanceof Point ) && !( g instanceof Curve ) && !( g instanceof Surface ) ) {
238 throw new RPCException( Messages.getMessage( "IGEO_STD_WFS_UNSERVED_GEOM", g.getClass().getName() ) );
239 }
240 }
241 }
242
243 /**
244 * The FEATURE_TYPE passed in the rpc must be given with namespace and featuretype name in the
245 * form: {http://some.address.com}:featureTypeName. This string gets extracted from the rpc and
246 * transformed into a <code>QualifiedName</code>.
247 *
248 * @param rpcEvent
249 * @return the QualifiedNames for the featureType in the passed rpcEvent
250 * @throws WFSClientException
251 * if featureType cannot be transformed to a <code>QualifiedName</code>.
252 */
253 protected QualifiedName extractFeatureTypeAsQualifiedName( RPCWebEvent rpcEvent )
254 throws WFSClientException {
255
256 RPCParameter[] params = rpcEvent.getRPCMethodCall().getParameters();
257 String featureType = (String) params[0].getValue();
258
259 String ns = featureType.substring( ( 1 + featureType.indexOf( "{" ) ), featureType.indexOf( "}:" ) );
260 String ftName = featureType.substring( 2 + featureType.indexOf( "}:" ) );
261
262 try {
263 return new QualifiedName( null, ftName, new URI( ns ) );
264 } catch ( URISyntaxException e ) {
265 LOG.logError( e.getLocalizedMessage() );
266 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_INVALID_NS", featureType, ns ) );
267 }
268 }
269
270 /**
271 * Create a WFS request (transaction) from the params given in the rpc event and the module
272 * configuration.
273 *
274 * @param rpcEvent
275 * @param transactionType
276 * the type of WFS transaction (might be "INSERT", "UPDATE", "DELETE")
277 * @return the request as string
278 * @throws WFSClientException
279 * if featureType in rpcEvent cannot be transformed to a <code>QualifiedName</code>
280 * or if the query template could not be read.
281 */
282 protected String createWFSTransactionRequest( RPCWebEvent rpcEvent, String transactionType )
283 throws WFSClientException {
284
285 String request = null;
286 QualifiedName qualiName = extractFeatureTypeAsQualifiedName( rpcEvent );
287 String wfsTemplate = null;
288
289 if ( "INSERT".equals( transactionType ) ) {
290 wfsTemplate = config.getFeatureTypeInsertTemplate( qualiName );
291 } else if ( "UPDATE".equals( transactionType ) ) {
292 wfsTemplate = config.getFeatureTypeUpdateTemplate( qualiName );
293 } else if ( "DELETE".equals( transactionType ) ) {
294 wfsTemplate = config.getFeatureTypeDeleteTemplate( qualiName );
295 }
296
297 if ( !new File( wfsTemplate ).isAbsolute() ) {
298 wfsTemplate = getHomePath() + wfsTemplate;
299 }
300
301 StringBuffer template = new StringBuffer( 10000 );
302 try {
303 // XMLFragment transactionFrag = new XMLFragment();
304 // transactionFrag.load( new URL( wfsTemplate ) );
305 // template.append( transactionFrag.toString() );
306
307 BufferedReader br = new BufferedReader( new FileReader( wfsTemplate ) );
308 String line = null;
309 while ( ( line = br.readLine() ) != null ) {
310 template.append( line );
311 }
312 br.close();
313 } catch ( IOException e ) {
314 LOG.logError( e.getLocalizedMessage(), e );
315 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_WRONG_TEMPLATE", wfsTemplate ) );
316 }
317 request = template.toString();
318
319 // SUBSTITUTE VARIABLES IN TEMPLATE
320 request = replaceTemplateVars( request, rpcEvent );
321
322 return request;
323 }
324
325 /**
326 * Send the request (e.g. wfs:Transaction, wfs:GetFeature) to the WFS of the passed address and
327 * return the received WFS response as XMLFragment.
328 *
329 * @param request
330 * the request for the WFS
331 * @param wfsAddress
332 * the address of the WFS
333 * @return the response by the WFS as XMLFragment.
334 * @throws WFSClientException
335 * if request agains wfsAddress failed, or if wfs response could not be loaded as
336 * XMLFragment
337 */
338 protected XMLFragment performRequest( String request, String wfsAddress )
339 throws WFSClientException {
340
341 if( LOG.getLevel() == ILogger.LOG_DEBUG ){
342 String msg = StringTools.concat( 300, "performRequest \n", request, "\nto wfsAddress ", wfsAddress );
343 LOG.logDebug( msg );
344 }
345
346 XMLFragment xmlFrag = null;
347
348 StringRequestEntity re = new StringRequestEntity( request );
349 PostMethod post = new PostMethod( wfsAddress );
350 post.setRequestEntity( re );
351 post.setRequestHeader( "Content-type", "text/xml;charset=" + CharsetUtils.getSystemCharset() );
352 InputStream is = null;
353 try {
354 HttpClient client = new HttpClient();
355 client = WebUtils.enableProxyUsage( client, new URL( wfsAddress ) );
356 client.executeMethod( post );
357 is = post.getResponseBodyAsStream();
358 } catch ( IOException e ) {
359 LOG.logError( e.getLocalizedMessage(), e );
360 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_QUERY_FAILED", wfsAddress ) );
361 }
362
363 xmlFrag = new XMLFragment();
364 try {
365 InputStreamReader isr = new InputStreamReader( is, CharsetUtils.getSystemCharset() );
366 xmlFrag.load( isr, wfsAddress );
367 } catch ( Exception e ) {
368 LOG.logError( e.getLocalizedMessage(), e );
369 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_LOAD_XMLFRAG_FAILED" ) );
370 }
371
372 return xmlFrag;
373 }
374
375 /**
376 * Handle the WFS transaction response.
377 *
378 * A positive response may be overwritten in method handlePositiveResponse(). A negative
379 * response may be overwritten in method handleNegativeResponse().
380 *
381 * @param response
382 * @return any objects that are returned from handlePositiveResponse() and that might be needed
383 * in derived listeners. Here, it returns null.
384 * @throws WFSClientException
385 * if the transaction response does not contain a root element of
386 * "wfs:TransactionResponse"
387 */
388 protected Object[] handleResponse( XMLFragment response )
389 throws WFSClientException {
390
391 Object[] obj = null;
392
393 Element rootElem = response.getRootElement();
394 String root = rootElem.getNodeName();
395
396 if( LOG.getLevel() == ILogger.LOG_DEBUG ){
397 String msg = StringTools.concat( 100, "Response root name: " + root );
398 LOG.logDebug( msg );
399 }
400 // root.contains("TransactionResponse")
401 if ( "wfs:TransactionResponse".equals( root ) ) {
402 obj = handlePositiveResponse( response );
403 } else if ( "ServiceExceptionReport".equals( root ) ) {
404 handleNegativeResponse( response );
405 } else {
406 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_UNKNOWN_TRANSACTION_RESPONSE", root ) );
407 }
408
409 return obj;
410 }
411
412 /**
413 * A positive response by the WFS needs to be handled individually in each derived Listener.
414 * Override the method, if needed.
415 *
416 * In this basic implementation, it sets the request attribute MESSAGE with the value of
417 * i18n.IGEO_STD_WFS_TRANSACTION_SUCCESS
418 *
419 * @param response
420 * @return any objects that might be needed. Here, it returns null.
421 */
422 protected Object[] handlePositiveResponse( XMLFragment response ) {
423 this.getRequest().setAttribute( Constants.MESSAGE, Messages.getMessage( "IGEO_STD_WFS_TRANSACTION_SUCCESS" ) );
424 return null;
425 }
426
427 /**
428 * A negative response by the WFS needs to be handled individually in each derived Listener.
429 * Override the method, if needed.
430 *
431 * In this basic implementation, it throws a WFSClientException IGEO_STD_WFS_TRANSACTION_FAILED,
432 * containing the message of the ServiceException element of the response.
433 *
434 * @param response
435 * @throws WFSClientException
436 * in any case. always.
437 */
438 protected void handleNegativeResponse( XMLFragment response )
439 throws WFSClientException {
440 Element rootElement = response.getRootElement();
441 NodeList errors = rootElement.getElementsByTagName( "ServiceException" );
442 throw new WFSClientException( Messages.getMessage( "IGEO_STD_WFS_TRANSACTION_FAILED",
443 errors.item( 0 ).getTextContent() ) );
444 }
445
446 /**
447 * This method is used to replace all placeholders in the template with values given in the
448 * struct of the second rpc parameter only. For the replacement to work, the placeholders in the
449 * template need to have the same name ($XYZ) as the struct members (XYZ) in the rpc.
450 *
451 * @param request
452 * @param rpcEvent
453 * @return the passed request after replacing the templates variables
454 */
455 private String replaceTemplateVars( String request, RPCWebEvent rpcEvent ) {
456
457 // standard replacements
458 RPCParameter[] params = rpcEvent.getRPCMethodCall().getParameters();
459 RPCStruct rpcStruct = (RPCStruct) params[1].getValue();
460 RPCMember[] members = rpcStruct.getMembers();
461 for ( int i = 0; i < members.length; i++ ) {
462
463 if ( "GEOMETRY".equals( members[i].getName() ) ) {
464 String geom = (String) members[i].getValue();
465 // geom = "POLYGON(( 12,34 13,35 ))";
466 geom = geom.substring( geom.lastIndexOf( '(' ) + 1, geom.indexOf( ')' ) ).trim();
467 request = StringTools.replace( request, "$GEOMETRY", geom, true );
468 } else {
469 request = StringTools.replace( request, "$" + members[i].getName(), (String) members[i].getValue(),
470 true );
471 }
472 }
473 return request;
474 }
475
476 /**
477 * this method will be overwritten in derived listeners
478 *
479 * @param request
480 * @param args
481 * any number of Objects needed in this method
482 * @return the request, with all special variables replaced. In this implementation, it returns
483 * the original request.
484 * @throws WFSClientException
485 */
486 protected String replaceSpecialTemplateVars( String request, Object... args )
487 throws WFSClientException {
488 // special replacements handled individually - only needed in derived Listeners
489 return request;
490 }
491
492 /**
493 * this method will be overwritten in derived listeners
494 *
495 * @param request
496 * @param args
497 * any number of Objects needed in this method
498 * @return the request, with all update variables replaced. In this implementation, it returns
499 * the original request.
500 * @throws WFSClientException
501 */
502 protected String replaceUpdateTemplateVars( String request, Object... args )
503 throws WFSClientException {
504 // special replacements handled individually - only needed in derived Listeners
505 return request;
506 }
507
508 /**
509 * this method will be overwritten in derived listeners
510 *
511 * @param request
512 * @param args
513 * any number of Objects needed in this method
514 * @return the request, with all delete variables replaced. In this implementation, it returns
515 * the original request.
516 * @throws WFSClientException
517 */
518 protected String replaceDeleteTemplateVars( String request, Object... args )
519 throws WFSClientException {
520 // special replacements handled individually - only needed in derived Listeners
521 return request;
522 }
523
524 }