001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }