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    }