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 }