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 }