001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/feature/Validator.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 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.io.datastore.schema.MappedFeatureType;
057 import org.deegree.model.feature.schema.FeaturePropertyType;
058 import org.deegree.model.feature.schema.FeatureType;
059 import org.deegree.model.feature.schema.GeometryPropertyType;
060 import org.deegree.model.feature.schema.MultiGeometryPropertyType;
061 import org.deegree.model.feature.schema.PropertyType;
062 import org.deegree.model.feature.schema.SimplePropertyType;
063 import org.deegree.model.spatialschema.Geometry;
064 import org.deegree.model.spatialschema.GeometryException;
065 import org.deegree.model.spatialschema.JTSAdapter;
066 import org.deegree.model.spatialschema.MultiSurface;
067 import org.deegree.model.spatialschema.Surface;
068 import org.deegree.model.spatialschema.SurfacePatch;
069 import org.deegree.ogcwebservices.OGCWebServiceException;
070
071 import com.vividsolutions.jts.algorithm.CGAlgorithms;
072 import com.vividsolutions.jts.geom.Coordinate;
073 import com.vividsolutions.jts.geom.CoordinateArrays;
074 import com.vividsolutions.jts.geom.GeometryFactory;
075 import com.vividsolutions.jts.geom.LinearRing;
076 import com.vividsolutions.jts.geom.MultiPolygon;
077 import com.vividsolutions.jts.geom.Polygon;
078 import com.vividsolutions.jts.geom.impl.CoordinateArraySequenceFactory;
079
080 /**
081 * Validator for feature instance (that have been constructed without schema information).
082 * <p>
083 * Validated features are assigned their respective feature types after successful validation.
084 *
085 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
086 * @author last edited by: $Author: apoth $
087 *
088 * @version $Revision: 9343 $, $Date: 2007-12-27 14:30:32 +0100 (Do, 27 Dez 2007) $
089 */
090 public class Validator {
091
092 private static final ILogger LOG = LoggerFactory.getLogger( Validator.class );
093
094 private Set<Feature> inValidation = new HashSet<Feature>();
095
096 private Map<QualifiedName, FeatureType> ftMap;
097
098 /**
099 * Constructs a new instance of <code>Validator</code> that will use the given map to lookup
100 * feature types by their names.
101 *
102 * @param ftMap
103 */
104 public Validator( Map<QualifiedName, FeatureType> ftMap ) {
105 this.ftMap = ftMap;
106 }
107
108 /**
109 * Validates the given feature instance (and its subfeatures).
110 * <p>
111 * The feature instance is then assigned the corresponding {@link MappedFeatureType}. This also
112 * applies to its subfeatures.
113 *
114 * @param feature
115 * feature instance to be validated
116 * @throws OGCWebServiceException
117 */
118 public void validate( Feature feature )
119 throws OGCWebServiceException {
120
121 if ( inValidation.contains( feature ) ) {
122 return;
123 }
124 inValidation.add( feature );
125
126 QualifiedName ftName = feature.getName();
127 FeatureType ft = this.ftMap.get( ftName );
128 if ( ft == null ) {
129 String msg = Messages.format( "ERROR_FT_UNKNOWN", feature.getId(), ftName );
130 throw new OGCWebServiceException( this.getClass().getName(), msg );
131 }
132
133 int idx = 0;
134 FeatureProperty[] properties = feature.getProperties();
135
136 PropertyType[] propertyTypes = ft.getProperties();
137 for ( int i = 0; i < propertyTypes.length; i++ ) {
138 idx += validateProperties( feature, propertyTypes[i], properties, idx );
139 }
140 if ( idx != properties.length ) {
141 String msg = Messages.format( "ERROR_FT_INVALID1", feature.getId(), ftName, properties[idx].getName() );
142 throw new OGCWebServiceException( this.getClass().getName(), msg );
143 }
144
145 feature.setFeatureType( ft );
146 }
147
148 /**
149 * Validates that there is the correct amount of properties with the expected type in the given
150 * array of properties.
151 *
152 * @param feature
153 * @param propertyType
154 * @param properties
155 * @param idx
156 * @throws OGCWebServiceException
157 */
158 private int validateProperties( Feature feature, PropertyType propertyType, FeatureProperty[] properties, int idx )
159 throws OGCWebServiceException {
160 int minOccurs = propertyType.getMinOccurs();
161 int maxOccurs = propertyType.getMaxOccurs();
162 QualifiedName propertyName = propertyType.getName();
163 int count = 0;
164
165 while ( idx + count < properties.length ) {
166 if ( properties[idx + count].getName().equals( propertyName ) ) {
167 validate( feature, properties[idx + count], propertyType );
168 count++;
169 } else {
170 break;
171 }
172 }
173 if ( count < minOccurs ) {
174 if ( count == 0 ) {
175 String msg = Messages.format( "ERROR_FT_INVALID2", feature.getId(), feature.getName(), propertyName );
176 throw new OGCWebServiceException( this.getClass().getName(), msg );
177 }
178 String msg = Messages.format( "ERROR_FT_INVALID3", feature.getId(), feature.getName(), propertyName,
179 minOccurs, count );
180 throw new OGCWebServiceException( this.getClass().getName(), msg );
181
182 }
183 if ( maxOccurs != -1 && count > maxOccurs ) {
184 String msg = Messages.format( "ERROR_FT_INVALID4", feature.getId(), feature.getName(), propertyName,
185 maxOccurs, count );
186 throw new OGCWebServiceException( this.getClass().getName(), msg );
187 }
188 return count;
189 }
190
191 /**
192 * Validates that there is the correct amount of properties with the expected type in the given
193 * array of properties.
194 *
195 * @param feature
196 * @param property
197 * @param pt
198 * @throws OGCWebServiceException
199 */
200 private void validate( Feature feature, FeatureProperty property, PropertyType pt )
201 throws OGCWebServiceException {
202
203 Object value = property.getValue();
204 if ( pt instanceof SimplePropertyType ) {
205 if ( pt.getType() != Types.ANYTYPE ) {
206 String s = value.toString();
207 if ( value instanceof Date ) {
208 s = TimeTools.getISOFormattedTime( (Date) value );
209 }
210 Object newValue = validateSimpleProperty( feature, (SimplePropertyType) pt, s );
211 property.setValue( newValue );
212 }
213 } else if ( pt instanceof GeometryPropertyType ) {
214 if ( !( value instanceof Geometry ) ) {
215 String msg = Messages.format( "ERROR_WRONG_PROPERTY_TYPE", feature.getId(), pt.getName(),
216 "GeometryProperty", value.getClass().getName() );
217 throw new OGCWebServiceException( this.getClass().getName(), msg );
218 }
219 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
220 Geometry correctedGeometry = validateGeometryProperty( feature, (GeometryPropertyType) pt,
221 (Geometry) value );
222 // property.setValue( correctedGeometry );
223 }
224 } else if ( pt instanceof FeaturePropertyType ) {
225 if ( !( value instanceof Feature ) ) {
226 String msg = Messages.format( "ERROR_WRONG_PROPERTY_TYPE", feature.getId(), pt.getName(),
227 "FeatureProperty", value.getClass().getName() );
228 throw new OGCWebServiceException( this.getClass().getName(), msg );
229 }
230 Feature subfeature = (Feature) value;
231 // FeaturePropertyContent content = (FeaturePropertyContent) propertyType.getContents()
232 // [0];
233 // MappedFeatureType contentFT = content.getFeatureTypeReference().getFeatureType();
234
235 // TODO: check that feature is a correct subsitution for the expected featuretype
236
237 validate( subfeature );
238 } else if ( pt instanceof MultiGeometryPropertyType ) {
239 throw new OGCWebServiceException( "Handling of MultiGeometryPropertyTypes not implemented "
240 + "in validateProperty()." );
241 } else {
242 throw new OGCWebServiceException( "Internal error: Unhandled property type '" + pt.getClass()
243 + "' encountered while validating property." );
244 }
245 }
246
247 /**
248 * Validates that the given string value can be converted to the type of the given
249 * {@link SimplePropertyType}.
250 *
251 * @param propertyType
252 * @param s
253 * @return corresponding <code>Object</code> for the string value
254 * @throws OGCWebServiceException
255 */
256 private Object validateSimpleProperty( Feature feature, SimplePropertyType propertyType, String s )
257 throws OGCWebServiceException {
258
259 int type = propertyType.getType();
260 QualifiedName propertyName = propertyType.getName();
261
262 Object value = null;
263 if ( type == Types.NUMERIC || type == Types.DOUBLE ) {
264 try {
265 value = new Double( s );
266 } catch ( NumberFormatException e ) {
267 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Double" );
268 throw new OGCWebServiceException( msg );
269 }
270 } else if ( type == Types.INTEGER ) {
271 try {
272 value = new Integer( s );
273 } catch ( NumberFormatException e ) {
274 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Integer" );
275 throw new OGCWebServiceException( msg );
276 }
277 } else if ( type == Types.DECIMAL || type == Types.FLOAT ) {
278 try {
279 value = new Float( s );
280 } catch ( NumberFormatException e ) {
281 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Float" );
282 throw new OGCWebServiceException( msg );
283 }
284 } else if ( type == Types.BOOLEAN ) {
285 value = new Boolean( s );
286 } else if ( type == Types.VARCHAR ) {
287 value = s;
288 } else if ( type == Types.DATE || type == Types.TIMESTAMP ) {
289 try {
290 value = TimeTools.createCalendar( s ).getTime();
291 } catch ( NumberFormatException e ) {
292 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, feature.getId(), "Date" );
293 throw new OGCWebServiceException( msg );
294 }
295 } else {
296 String typeString = "" + type;
297 try {
298 typeString = Types.getTypeNameForSQLTypeCode( type );
299 } catch ( UnknownTypeException e ) {
300 LOG.logError( "No type name for code: " + type );
301 }
302 String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString );
303 LOG.logError( msg );
304 throw new OGCWebServiceException( msg );
305 }
306 return value;
307 }
308
309 private Geometry validateGeometryProperty( Feature feature, GeometryPropertyType pt, Geometry geometry ) {
310
311 try {
312 com.vividsolutions.jts.geom.Geometry jtsGeometry = JTSAdapter.export( geometry );
313 if ( !jtsGeometry.isValid() ) {
314 String msg = Messages.format( "GEOMETRY_NOT_VALID", pt.getName(), feature.getId() );
315 LOG.logDebug( msg );
316 } else if ( geometry instanceof Surface ) {
317 geometry = validatePolygonOrientation( feature, pt, (Surface) geometry, (Polygon) jtsGeometry );
318 } else if ( geometry instanceof MultiSurface ) {
319 geometry = validateMultiPolygonOrientation( feature, pt, (MultiSurface) geometry,
320 (MultiPolygon) jtsGeometry );
321 }
322 } catch ( GeometryException e ) {
323 LOG.logError( e.getMessage(), e );
324 }
325 return geometry;
326 }
327
328 /**
329 * Checks whether the outer boundary of the given {@link Surface} geometry has counter-clockwise
330 * orientation and that the inner boundaries have clockwise orientation (as specified by ISO
331 * 19107 / GML).
332 * <p>
333 * Information on invalid orientations is logged.
334 *
335 * @param feature
336 * @param pt
337 * @param surface
338 * @param polygon
339 * @throws GeometryException
340 */
341 private Surface validatePolygonOrientation( Feature feature, GeometryPropertyType pt, Surface surface,
342 Polygon polygon )
343 throws GeometryException {
344 GeometryFactory factory = new GeometryFactory();
345 CoordinateArraySequenceFactory coordSeqFactory = CoordinateArraySequenceFactory.instance();
346
347 Coordinate[] outerCoords = polygon.getExteriorRing().getCoordinates();
348 if ( !CGAlgorithms.isCCW( outerCoords ) ) {
349 String msg = Messages.format( "OUTER_RING_NOT_CCW", pt.getName(), feature.getId() );
350 LOG.logDebug( msg );
351 CoordinateArrays.reverse( outerCoords );
352 }
353 LinearRing shell = new LinearRing( coordSeqFactory.create( outerCoords ), factory );
354
355 LinearRing[] holes = new LinearRing[polygon.getNumInteriorRing()];
356 for ( int i = 0; i < polygon.getNumInteriorRing(); i++ ) {
357 Coordinate[] innerCoords = polygon.getInteriorRingN( i ).getCoordinates();
358 if ( CGAlgorithms.isCCW( innerCoords ) ) {
359 String msg = Messages.format( "INNER_RING_NOT_CW", i, pt.getName(), feature.getId() );
360 LOG.logDebug( msg );
361 CoordinateArrays.reverse( innerCoords );
362 }
363 holes[i] = new LinearRing( coordSeqFactory.create( innerCoords ), factory );
364 }
365 Surface correctedSurface = (Surface) JTSAdapter.wrap( new Polygon( shell, holes, factory ) );
366 SurfacePatch[] patches = new SurfacePatch[correctedSurface.getNumberOfSurfacePatches()];
367 for ( int i = 0; i < patches.length; i++ ) {
368 patches[i] = correctedSurface.getSurfacePatchAt( 0 );
369 }
370 return org.deegree.model.spatialschema.GeometryFactory.createSurface( patches, surface.getCoordinateSystem() );
371 }
372
373 /**
374 * Checks whether the outer boundaries of the given {@link MultiSurface} members have
375 * counter-clockwise orientation and that the inner boundaries have clockwise orientation (as
376 * specified by ISO 19107 / GML).
377 * <p>
378 * Information on invalid orientations is logged.
379 *
380 * @param feature
381 * @param pt
382 * @param multiSurface
383 * @param multiPolygon
384 * @throws GeometryException
385 */
386 private MultiSurface validateMultiPolygonOrientation( Feature feature, GeometryPropertyType pt,
387 MultiSurface multiSurface, MultiPolygon multiPolygon )
388 throws GeometryException {
389 Surface[] surfaces = new Surface[multiPolygon.getNumGeometries()];
390 for ( int i = 0; i < surfaces.length; i++ ) {
391 surfaces[i] = validatePolygonOrientation( feature, pt, multiSurface.getSurfaceAt( i ),
392 (Polygon) multiPolygon.getGeometryN( i ) );
393 }
394 return org.deegree.model.spatialschema.GeometryFactory.createMultiSurface( surfaces,
395 multiSurface.getCoordinateSystem() );
396 }
397 }