001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/portal/standard/wfs/control/GeometryValidator.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003     This file is part of deegree.
004     Copyright (C) 2001-2008 by:
005     Department of Geography, University of Bonn
006     http://www.giub.uni-bonn.de/deegree/
007     lat/lon GmbH
008     http://www.lat-lon.de
009    
010     This library is free software; you can redistribute it and/or
011     modify it under the terms of the GNU Lesser General Public
012     License as published by the Free Software Foundation; either
013     version 2.1 of the License, or (at your option) any later version.
014    
015     This library is distributed in the hope that it will be useful,
016     but WITHOUT ANY WARRANTY; without even the implied warranty of
017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018     Lesser General Public License for more details.
019    
020     You should have received a copy of the GNU Lesser General Public
021     License along with this library; if not, write to the Free Software
022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023    
024     Contact:
025    
026     Andreas Poth
027     lat/lon GmbH
028     Aennchenstr. 19
029     53177 Bonn
030     Germany
031     E-Mail: poth@lat-lon.de
032    
033     Prof. Dr. Klaus Greve
034     Department of Geography
035     University of Bonn
036     Meckenheimer Allee 166
037     53115 Bonn
038     Germany
039     E-Mail: greve@giub.uni-bonn.de
040    
041     ---------------------------------------------------------------------------*/
042    
043    package org.deegree.portal.standard.wfs.control;
044    
045    import javax.servlet.http.HttpServletRequest;
046    
047    import org.deegree.enterprise.control.AbstractListener;
048    import org.deegree.enterprise.control.FormEvent;
049    import org.deegree.enterprise.control.RPCParameter;
050    import org.deegree.enterprise.control.RPCStruct;
051    import org.deegree.enterprise.control.RPCUtils;
052    import org.deegree.enterprise.control.RPCWebEvent;
053    import org.deegree.framework.log.ILogger;
054    import org.deegree.framework.log.LoggerFactory;
055    import org.deegree.i18n.Messages;
056    import org.deegree.model.spatialschema.Geometry;
057    import org.deegree.model.spatialschema.JTSAdapter;
058    import org.deegree.model.spatialschema.WKTAdapter;
059    import org.deegree.portal.PortalException;
060    
061    import com.vividsolutions.jts.algorithm.CGAlgorithms;
062    
063    /**
064     * This listener validates simple geometries. The geometries to be validated are passed in as the
065     * value of the <code>'GEOMETRY'</code> parameter of such an RPC request:
066     * 
067     * <pre>
068     *   &lt;methodCall&gt;
069     *   &lt;methodName&gt;dig:checkGeometry&lt;/methodName&gt;
070     *   &lt;params&gt;
071     *   &lt;param&gt;&lt;value&gt;&lt;struct&gt;&lt;member&gt;
072     *   &lt;name&gt;GEOMETRY&lt;/name&gt;
073     *   &lt;value&gt;&lt;string&gt;POLYGON(( 7.38 49.64, 12.03 48.7, 11.34 51.66, 11.34 51.66, 7.38 49.64 ))&lt;/string&gt;&lt;/value&gt;
074     *   &lt;/member&gt;&lt;/struct&gt;&lt;/value&gt;&lt;/param&gt;
075     *   &lt;/params&gt;
076     *   &lt;/methodCall&gt;
077     *  
078     * </pre>
079     * 
080     * Subclasses may override {@link #validateRequest( RPCWebEvent rpcEvent )},
081     * {@link #createGeometry( RPCWebEvent rpcEvent )} and/or validateGeometry() if they want to change
082     * the behaviour regarding request validation, geometry creation and/or geometry validation,
083     * respectively. Especially the last method may be different according to use-cases. The result of
084     * the validation is put in the session under the key <code>GeometryValidator.GEOMETRY_STATE</code>.
085     * <br/>
086     * 
087     * 
088     * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
089     * @author last edited by: $Author: apoth $
090     * 
091     * @version $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 2007) $
092     */
093    public class GeometryValidator extends AbstractListener {
094    
095        private static final ILogger LOG = LoggerFactory.getLogger( GeometryValidator.class );
096    
097        public static final String GEOMETRY_STATE = "GEOMETRY_STATE";
098    
099        public static final String VALID_GEOMETRY = "VALID_GEOMETRY";
100    
101        public static final String INVALID_GEOMETRY = "INVALID_GEOMETRY";
102    
103        public static final String INFORMALLY_VALID_GEOMETRY = "INFORMALLY_VALID_GEOMETRY";
104    
105        protected Geometry geometry;
106    
107        /**
108         * This method validates the RPC-encoded request ({@link #validateRequest(RPCWebEvent)}),
109         * create some geometry from the request parameters ({@link #createGeometry(RPCWebEvent)} and
110         * then validate this geometry ({@link #validateGeometry()}. The result of this validate is
111         * made available to the request object as an attribute under the key
112         * <code>GeometryValidator.GEOMETRY_STATE</code>.
113         * 
114         * @param event
115         *            the event containing the RPC-emcoded request.
116         */
117        public void actionPerformed( FormEvent event ) {
118    
119            RPCWebEvent rpcEvent = (RPCWebEvent) event;
120    
121            try {
122                validateRequest( rpcEvent );
123            } catch ( Exception e ) {
124                LOG.logError( e.getMessage(), e );
125                gotoErrorPage( Messages.getMessage( "IGEO_STD_INVALID_RPC", e.getLocalizedMessage() ) );
126                return;
127            }
128    
129            Object validationResult = INVALID_GEOMETRY;
130    
131            try {
132                geometry = createGeometry( rpcEvent );
133            } catch ( Exception e ) {
134                // do not consider this as an exception - and thus return the error
135                // mesg back to the page -, but as a failure to create a valid geometry
136                LOG.logDebug( e.getMessage(), e );
137            }
138    
139            try {
140                validationResult = validateGeometry();
141            } catch ( Exception e ) {
142                LOG.logError( e.getMessage(), e );
143                // TODO i18nize
144                gotoErrorPage( e.getLocalizedMessage() );
145                return;
146            }
147    
148            HttpServletRequest req = (HttpServletRequest) getRequest();
149            req.setAttribute( GEOMETRY_STATE, validationResult );
150    
151        }
152    
153        /**
154         * Validates the incoming RPc request. This method looks for an RPC parameter (struct) named
155         * <code>'GEOMETRY'</code>, checks if the valeu is empty and looks no further. The expected
156         * value is a well-known geometry representation.
157         * 
158         * @param rpcEvent
159         *            the event containing at least a struct with a string type parameter named
160         *            <code>'GEOMETRY'</code>.
161         * @throws Exception
162         *             If no such parameter is found or if the value is empty. This is interpreted as an
163         *             exception and the resulting message is sent back to the client.
164         */
165        protected void validateRequest( RPCWebEvent rpcEvent )
166                                throws Exception {
167    
168            RPCParameter[] pars = rpcEvent.getRPCMethodCall().getParameters();
169            if ( pars == null || pars.length != 1 ) {
170                throw new PortalException( "The RPC request must contain a 'GEOMETRY' parameter." );
171            }
172            RPCStruct struct = (RPCStruct) pars[0].getValue();
173    
174            String geomString = RPCUtils.getRpcPropertyAsString( struct, "GEOMETRY" );
175            if ( geomString == null || geomString.length() == 0 ) {
176                // TODO i18nize
177                throw new PortalException( "The RPC request does not contains a well-known "
178                                           + " text representation of a geometry." );
179            }
180    
181        }
182    
183        /**
184         * Creates the geometry from the data provided it the request (rpcEvent).
185         * 
186         * @see #validateRequest(RPCWebEvent)
187         * @param rpcEvent
188         *            the <code>RPCWebEvent</code> conatining the request
189         * @return a new geometry. This method uses
190         *         {@link WKTAdapter#wrap(String, org.deegree.model.crs.CoordinateSystem)} to create the
191         *         new <code>Geometry</code>.
192         * @throws Exception
193         *             if the <code>Geometry</code> creation failed.
194         * 
195         */
196        protected Geometry createGeometry( RPCWebEvent rpcEvent )
197                                throws Exception {
198            RPCParameter[] pars = rpcEvent.getRPCMethodCall().getParameters();
199            RPCStruct struct = (RPCStruct) pars[0].getValue();
200            String geomString = RPCUtils.getRpcPropertyAsString( struct, "GEOMETRY" );
201            LOG.logDebug( "Digitized geometry: \n" + geomString );
202            return WKTAdapter.wrap( geomString, null );
203        }
204    
205        /**
206         * Validates this object's <code>Geometry</code> and return an object, preferably a code,
207         * indicating the validity state of the geometry. There are three type of geometry validity
208         * states.
209         * <ul>
210         * <li><code>GeometryValidator.VALID_GEOMETRY</code>: The geometry is perfecly OK,</li>
211         * <li><code>GeometryValidator.INVALID_GEOMETRY</code>: The geometry is not OK (self
212         * intersection, missing points, etc),</li>
213         * <li><code>GeometryValidator.INFORMALLY_VALID_GEOMETRY</code>: The geometry is OK,
214         * although it may not be considered formally correct in most GIS contexts. For example,
215         * polygons are supposed to have counter-clockwise outter rings, lines may no self-intersect,
216         * etc.</li>
217         * </ul>
218         * <br/> The meaning of those may also vary according to the use-case, e.g. a polygon that is
219         * counter-clockwise but is required to have at least 4 vertices. <br/> This method employs the
220         * Java Topology Suite to check for geometry validity ({@link com.vividsolutions.jts.geom.Geometry#isValid()})
221         * and, in the case of polygons, for the order of the coordinates (in other words, whether they
222         * are counter-clockwise}. <br/> The result of the validation is available in the request under
223         * the key <code>GeometryValidator.GEOMETRY_STATE</code>.
224         * 
225         * @return
226         */
227        protected Object validateGeometry() {
228    
229            String result = INVALID_GEOMETRY;
230    
231            try {
232                com.vividsolutions.jts.geom.Geometry jtsGeom = JTSAdapter.export( geometry );
233                if ( jtsGeom.isValid() ) { // means is OK, really
234                    result = VALID_GEOMETRY;
235                    if ( jtsGeom instanceof com.vividsolutions.jts.geom.Polygon ) {
236                        if ( !CGAlgorithms.isCCW( jtsGeom.getCoordinates() ) ) {
237                            result = INFORMALLY_VALID_GEOMETRY;
238                        }
239                    }
240                }
241            } catch ( Exception e ) {
242                LOG.logError( e.getLocalizedMessage(), e );
243                // just in case
244                result = INVALID_GEOMETRY;
245            }
246    
247            LOG.logInfo( "Geometry validation result: " + result );
248    
249            return result;
250        }
251    
252    }