    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    }