001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/feature/Validator.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2006 by:
006 Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043 package org.deegree.model.feature;
044
045 import java.util.Date;
046 import java.util.HashSet;
047 import java.util.Map;
048 import java.util.Set;
049
050 import org.deegree.datatypes.QualifiedName;
051 import org.deegree.datatypes.Types;
052 import org.deegree.datatypes.UnknownTypeException;
053 import org.deegree.framework.log.ILogger;
054 import org.deegree.framework.log.LoggerFactory;
055 import org.deegree.framework.util.TimeTools;
056 import org.deegree.model.feature.schema.FeaturePropertyType;
057 import org.deegree.model.feature.schema.FeatureType;
058 import org.deegree.model.feature.schema.GeometryPropertyType;
059 import org.deegree.model.feature.schema.MultiGeometryPropertyType;
060 import org.deegree.model.feature.schema.PropertyType;
061 import org.deegree.model.feature.schema.SimplePropertyType;
062 import org.deegree.model.spatialschema.Geometry;
063 import org.deegree.model.spatialschema.GeometryException;
064 import org.deegree.model.spatialschema.JTSAdapter;
065 import org.deegree.model.spatialschema.MultiSurface;
066 import org.deegree.model.spatialschema.Surface;
067 import org.deegree.model.spatialschema.SurfacePatch;
068 import org.deegree.ogcwebservices.OGCWebServiceException;
069
070 import com.vividsolutions.jts.algorithm.CGAlgorithms;
071 import com.vividsolutions.jts.geom.Coordinate;
072 import com.vividsolutions.jts.geom.CoordinateArrays;
073 import com.vividsolutions.jts.geom.GeometryFactory;
074 import com.vividsolutions.jts.geom.LinearRing;
075 import com.vividsolutions.jts.geom.MultiPolygon;
076 import com.vividsolutions.jts.geom.Polygon;
077 import com.vividsolutions.jts.geom.impl.CoordinateArraySequenceFactory;
078
079 /**
080 * Validator for feature instance (that have been constructed without schema information).
081 * <p>
082 * Validated features are assigned their respective feature types after successful validation.
083 *
084 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
085 * @author last edited by: $Author: mschneider $
086 *
087 * @version $Revision: 7377 $, $Date: 2007-05-30 18:29:12 +0200 (Mi, 30 Mai 2007) $
088 */
089 public class Validator {
090
091 private static final ILogger LOG = LoggerFactory.getLogger( Validator.class );
092
093 private Set<Feature> inValidation = new HashSet<Feature>();
094
095 private Map<QualifiedName, FeatureType> ftMap;
096
097 /**
098 * Constructs a new instance of <code>Validator</code> that will use the given map to lookup feature types by
099 * their names.
100 *
101 * @param ftMap
102 */
103 public Validator( Map<QualifiedName, FeatureType> ftMap ) {
104 this.ftMap = ftMap;
105 }
106
107 /**
108 * Validates the given feature instance (and its subfeatures).
109 * <p>
110 * The feature instance is then assigned the corresponding {@link MappedFeatureType}. This also applies to its
111 * subfeatures.
112 *
113 * @param feature
114 * feature instance to be validated
115 * @throws OGCWebServiceException
116 */
117 public void validate( Feature feature )
118 throws OGCWebServiceException {
119
120 if ( inValidation.contains( feature ) ) {
121 return;
122 }
123 inValidation.add( feature );
124
125 QualifiedName ftName = feature.getName();
126 FeatureType ft = this.ftMap.get( ftName );
127 if ( ft == null ) {
128 String msg = Messages.format( "ERROR_FT_UNKNOWN", feature.getId(), ftName );
129 throw new OGCWebServiceException( this.getClass().getName(), msg );
130 }
131
132 int idx = 0;
133 FeatureProperty[] properties = feature.getProperties();
134
135 PropertyType[] propertyTypes = ft.getProperties();
136 for ( int i = 0; i < propertyTypes.length; i++ ) {
137 idx += validateProperties( feature, propertyTypes[i], properties, idx );
138 }
139 if ( idx != properties.length ) {
140 String msg = Messages.format( "ERROR_FT_INVALID1", feature.getId(), ftName, properties[idx].getName() );
141 throw new OGCWebServiceException( this.getClass().getName(), msg );
142 }
143
144 feature.setFeatureType( ft );
145 }
146
147 /**
148 * Validates that there is the correct amount of properties with the expected type in the given array of properties.
149 *
150 * @param feature
151 * @param propertyType
152 * @param properties
153 * @param idx
154 * @throws OGCWebServiceException
155 */
156 private int validateProperties( Feature feature, PropertyType propertyType, FeatureProperty[] properties, int idx )
157 throws OGCWebServiceException {
158 int minOccurs = propertyType.getMinOccurs();
159 int maxOccurs = propertyType.getMaxOccurs();
160 QualifiedName propertyName = propertyType.getName();
161 int count = 0;
162
163 while ( idx + count < properties.length ) {
164 if ( properties[idx + count].getName().equals( propertyName ) ) {
165 validate( feature, properties[idx + count], propertyType );
166 count++;
167 } else {
168 break;
169 }
170 }
171 if ( count < minOccurs ) {
172 if ( count == 0 ) {
173 String msg = Messages.format( "ERROR_FT_INVALID2", feature.getId(), feature.getName(), propertyName );
174 throw new OGCWebServiceException( this.getClass().getName(), msg );
175 }
176 String msg = Messages.format( "ERROR_FT_INVALID3", feature.getId(), feature.getName(), propertyName,
177 minOccurs, count );
178 throw new OGCWebServiceException( this.getClass().getName(), msg );
179
180 }
181 if ( maxOccurs != -1 && count > maxOccurs ) {
182 String msg = Messages.format( "ERROR_FT_INVALID4", feature.getId(), feature.getName(), propertyName,
183 maxOccurs, count );
184 throw new OGCWebServiceException( this.getClass().getName(), msg );
185 }
186 return count;
187 }
188
189 /**
190 * Validates that there is the correct amount of properties with the expected type in the given array of properties.
191 *
192 * @param feature
193 * @param property
194 * @param pt
195 * @throws OGCWebServiceException
196 */
197 private void validate( Feature feature, FeatureProperty property, PropertyType pt )
198 throws OGCWebServiceException {
199
200 Object value = property.getValue();
201 if ( pt instanceof SimplePropertyType ) {
202 String s = value.toString();
203 if ( value instanceof Date ) {
204 s = TimeTools.getISOFormattedTime( (Date) value );
205 }
206 Object newValue = validateSimpleProperty( feature, (SimplePropertyType) pt, s );
207 property.setValue( newValue );
208 } else if ( pt instanceof GeometryPropertyType ) {
209 if ( !( value instanceof Geometry ) ) {
210 String msg = Messages.format( "ERROR_WRONG_PROPERTY_TYPE", feature.getId(), pt.getName(),
211 "GeometryProperty", value.getClass().getName() );
212 throw new OGCWebServiceException( this.getClass().getName(), msg );
213 }
214 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
215 Geometry correctedGeometry = validateGeometryProperty( feature, (GeometryPropertyType) pt,
216 (Geometry) value );
217 // property.setValue( correctedGeometry );
218 }
219 } else if ( pt instanceof FeaturePropertyType ) {
220 if ( !( value instanceof Feature ) ) {
221 String msg = Messages.format( "ERROR_WRONG_PROPERTY_TYPE", feature.getId(), pt.getName(),
222 "FeatureProperty", value.getClass().getName() );
223 throw new OGCWebServiceException( this.getClass().getName(), msg );
224 }
225 Feature subfeature = (Feature) value;
226 // FeaturePropertyContent content = (FeaturePropertyContent) propertyType.getContents()
227 // [0];
228 // MappedFeatureType contentFT = content.getFeatureTypeReference().getFeatureType();
229
230 // TODO: check that feature is a correct subsitution for the expected featuretype
231
232 validate( subfeature );
233 } else if ( pt instanceof MultiGeometryPropertyType ) {
234 throw new OGCWebServiceException( "Handling of MultiGeometryPropertyTypes not implemented "
235 + "in validateProperty()." );
236 } else {
237 throw new OGCWebServiceException( "Internal error: Unhandled property type '" + pt.getClass()
238 + "' encountered while validating property." );
239 }
240 }
241
242 /**
243 * Validates that the given string value can be converted to the type of the given {@link SimplePropertyType}.
244 *
245 * @param propertyType
246 * @param s
247 * @return corresponding <code>Object</code> for the string value
248 * @throws OGCWebServiceException
249 */
250 private Object validateSimpleProperty( Feature feature, SimplePropertyType propertyType, String s )
251 throws OGCWebServiceException {
252
253 int type = propertyType.getType();
254 QualifiedName propertyName = propertyType.getName();
255
256 Object value = null;
257 if ( type == Types.NUMERIC || type == Types.DOUBLE ) {
258 try {
259 value = new Double( s );
260 } catch ( NumberFormatException e ) {
261 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Double" );
262 throw new OGCWebServiceException( msg );
263 }
264 } else if ( type == Types.INTEGER ) {
265 try {
266 value = new Integer( s );
267 } catch ( NumberFormatException e ) {
268 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Integer" );
269 throw new OGCWebServiceException( msg );
270 }
271 } else if ( type == Types.DECIMAL || type == Types.FLOAT ) {
272 try {
273 value = new Float( s );
274 } catch ( NumberFormatException e ) {
275 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Float" );
276 throw new OGCWebServiceException( msg );
277 }
278 } else if ( type == Types.BOOLEAN ) {
279 value = new Boolean( s );
280 } else if ( type == Types.VARCHAR ) {
281 value = s;
282 } else if ( type == Types.DATE || type == Types.TIMESTAMP ) {
283 try {
284 value = TimeTools.createCalendar( s ).getTime();
285 } catch ( NumberFormatException e ) {
286 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Date" );
287 throw new OGCWebServiceException( msg );
288 }
289 } else {
290 String typeString = "" + type;
291 try {
292 typeString = Types.getTypeNameForSQLTypeCode( type );
293 } catch ( UnknownTypeException e ) {
294 LOG.logError( "No type name for code: " + type );
295 }
296 String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString );
297 LOG.logError( msg );
298 throw new OGCWebServiceException( msg );
299 }
300 return value;
301 }
302
303 private Geometry validateGeometryProperty( Feature feature, GeometryPropertyType pt, Geometry geometry ) {
304
305 try {
306 com.vividsolutions.jts.geom.Geometry jtsGeometry = JTSAdapter.export( geometry );
307 if ( !jtsGeometry.isValid() ) {
308 String msg = Messages.format( "GEOMETRY_NOT_VALID", pt.getName(), feature.getId() );
309 LOG.logDebug( msg );
310 } else if ( geometry instanceof Surface ) {
311 geometry = validatePolygonOrientation( feature, pt, (Surface) geometry, (Polygon) jtsGeometry );
312 } else if ( geometry instanceof MultiSurface ) {
313 geometry = validateMultiPolygonOrientation( feature, pt, (MultiSurface) geometry,
314 (MultiPolygon) jtsGeometry );
315 }
316 } catch ( GeometryException e ) {
317 LOG.logError( e.getMessage(), e );
318 }
319 return geometry;
320 }
321
322 /**
323 * Checks whether the outer boundary of the given {@link Surface} geometry has counter-clockwise orientation and
324 * that the inner boundaries have clockwise orientation (as specified by ISO 19107 / GML).
325 * <p>
326 * Information on invalid orientations is logged.
327 *
328 * @param feature
329 * @param pt
330 * @param surface
331 * @param polygon
332 * @throws GeometryException
333 */
334 private Surface validatePolygonOrientation( Feature feature, GeometryPropertyType pt, Surface surface,
335 Polygon polygon )
336 throws GeometryException {
337 GeometryFactory factory = new GeometryFactory();
338 CoordinateArraySequenceFactory coordSeqFactory = CoordinateArraySequenceFactory.instance();
339
340 Coordinate[] outerCoords = polygon.getExteriorRing().getCoordinates();
341 if ( !CGAlgorithms.isCCW( outerCoords ) ) {
342 String msg = Messages.format( "OUTER_RING_NOT_CCW", pt.getName(), feature.getId() );
343 LOG.logDebug( msg );
344 CoordinateArrays.reverse( outerCoords );
345 }
346 LinearRing shell = new LinearRing( coordSeqFactory.create( outerCoords ), factory );
347
348 LinearRing[] holes = new LinearRing[polygon.getNumInteriorRing()];
349 for ( int i = 0; i < polygon.getNumInteriorRing(); i++ ) {
350 Coordinate[] innerCoords = polygon.getInteriorRingN( i ).getCoordinates();
351 if ( CGAlgorithms.isCCW( innerCoords ) ) {
352 String msg = Messages.format( "INNER_RING_NOT_CW", i, pt.getName(), feature.getId() );
353 LOG.logDebug( msg );
354 CoordinateArrays.reverse( innerCoords );
355 }
356 holes[i] = new LinearRing( coordSeqFactory.create( innerCoords ), factory );
357 }
358 Surface correctedSurface = (Surface) JTSAdapter.wrap( new Polygon( shell, holes, factory ) );
359 SurfacePatch[] patches = new SurfacePatch[correctedSurface.getNumberOfSurfacePatches()];
360 for ( int i = 0; i < patches.length; i++ ) {
361 patches[i] = correctedSurface.getSurfacePatchAt( 0 );
362 }
363 return org.deegree.model.spatialschema.GeometryFactory.createSurface( patches, surface.getCoordinateSystem() );
364 }
365
366 /**
367 * Checks whether the outer boundaries of the given {@link MultiSurface} members have counter-clockwise orientation
368 * and that the inner boundaries have clockwise orientation (as specified by ISO 19107 / GML).
369 * <p>
370 * Information on invalid orientations is logged.
371 *
372 * @param feature
373 * @param pt
374 * @param multiSurface
375 * @param multiPolygon
376 * @throws GeometryException
377 */
378 private MultiSurface validateMultiPolygonOrientation( Feature feature, GeometryPropertyType pt,
379 MultiSurface multiSurface, MultiPolygon multiPolygon )
380 throws GeometryException {
381 Surface[] surfaces = new Surface[multiPolygon.getNumGeometries()];
382 for ( int i = 0; i < surfaces.length; i++ ) {
383 surfaces[i] = validatePolygonOrientation( feature, pt, multiSurface.getSurfaceAt( i ),
384 (Polygon) multiPolygon.getGeometryN( i ) );
385 }
386 return org.deegree.model.spatialschema.GeometryFactory.createMultiSurface( surfaces,
387 multiSurface.getCoordinateSystem() );
388 }
389 }