001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/feature/schema/GMLSchema.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2008 by: 006 EXSE, 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.schema; 044 045 import java.net.URI; 046 import java.util.ArrayList; 047 import java.util.HashMap; 048 import java.util.HashSet; 049 import java.util.Iterator; 050 import java.util.List; 051 import java.util.Map; 052 import java.util.Set; 053 054 import org.deegree.datatypes.QualifiedName; 055 import org.deegree.datatypes.Types; 056 import org.deegree.datatypes.UnknownTypeException; 057 import org.deegree.framework.log.ILogger; 058 import org.deegree.framework.log.LoggerFactory; 059 import org.deegree.framework.xml.XMLParsingException; 060 import org.deegree.framework.xml.schema.ComplexTypeDeclaration; 061 import org.deegree.framework.xml.schema.ElementDeclaration; 062 import org.deegree.framework.xml.schema.SimpleTypeDeclaration; 063 import org.deegree.framework.xml.schema.UndefinedXSDTypeException; 064 import org.deegree.framework.xml.schema.XMLSchema; 065 import org.deegree.framework.xml.schema.XMLSchemaException; 066 import org.deegree.model.crs.UnknownCRSException; 067 import org.deegree.model.feature.FeatureFactory; 068 import org.deegree.ogcbase.CommonNamespaces; 069 070 /** 071 * Represents a GML application schema document to provide easy access to it's components, especially the 072 * {@link FeatureType} definitions. 073 * 074 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 075 * @author last edited by: $Author: apoth $ 076 * 077 * @version $Revision: 9343 $, $Date: 2007-12-27 14:30:32 +0100 (Do, 27 Dez 2007) $ 078 */ 079 public class GMLSchema extends XMLSchema { 080 081 private final static ILogger LOG = LoggerFactory.getLogger( GMLSchema.class ); 082 083 private static URI XSDNS = CommonNamespaces.XSNS; 084 085 private static URI GMLNS = CommonNamespaces.GMLNS; 086 087 private static final QualifiedName ABSTRACT_FEATURE = new QualifiedName( "_Feature", GMLNS ); 088 089 // keys: QualifiedNames (feature type names), values: FeatureTypes 090 protected Map<QualifiedName, FeatureType> featureTypeMap = new HashMap<QualifiedName, FeatureType>(); 091 092 // keys: FeatureTypes, values: List (of FeatureTypes) 093 protected Map<FeatureType, List<FeatureType>> substitutionMap = new HashMap<FeatureType, List<FeatureType>>(); 094 095 /** 096 * Creates a new <code>GMLSchema</code> instance from the given parameters. 097 * 098 * @param targetNamespace 099 * @param simpleTypes 100 * @param complexTypes 101 * @param elementDeclarations 102 * @throws XMLParsingException 103 * @throws UnknownCRSException 104 */ 105 public GMLSchema( URI targetNamespace, SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes, 106 ElementDeclaration[] elementDeclarations ) throws XMLParsingException, UnknownCRSException { 107 super( targetNamespace, simpleTypes, complexTypes, elementDeclarations ); 108 buildFeatureTypeMap( elementDeclarations ); 109 buildSubstitutionMap( elementDeclarations ); 110 } 111 112 // TODO remove this constructor 113 protected GMLSchema( ElementDeclaration[] elementDeclarations, URI targetNamespace, 114 SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes ) 115 throws XMLSchemaException { 116 super( targetNamespace, simpleTypes, complexTypes, elementDeclarations ); 117 } 118 119 /** 120 * Returns all {@link FeatureType}s that are defined in the schema. 121 * 122 * @return all FeatureTypes 123 */ 124 public FeatureType[] getFeatureTypes() { 125 return this.featureTypeMap.values().toArray( new FeatureType[this.featureTypeMap.size()] ); 126 } 127 128 /** 129 * Looks up the {@link FeatureType} with the given {@link QualifiedName}. 130 * 131 * @param qName 132 * the QualifiedName to look up 133 * @return the FeatureType, if it is defined in the document, null otherwise 134 */ 135 public FeatureType getFeatureType( QualifiedName qName ) { 136 return this.featureTypeMap.get( qName ); 137 } 138 139 /** 140 * Looks up the {@link FeatureType} with the given local name. 141 * 142 * @param localName 143 * the name to look up 144 * @return the FeatureType, if it is defined in the document, null otherwise 145 */ 146 public FeatureType getFeatureType( String localName ) { 147 return getFeatureType( new QualifiedName( localName, getTargetNamespace() ) ); 148 } 149 150 /** 151 * Return whether the given feature type has more than one concrete substitution. 152 * <p> 153 * Read as: Is there only one concrete feature type that all instances of this type must have? Or are there several 154 * possible concrete subtypes? 155 * 156 * @param ft 157 * feature type to check 158 * @return true, if the feature type has more than once concrete implementations, false otherwise 159 */ 160 public boolean hasSeveralImplementations( FeatureType ft ) { 161 return getSubstitutions( ft ).length > 1; 162 } 163 164 /** 165 * Returns all non-abstract implementations of a given feature type that are defined in this schema. 166 * 167 * @param featureType 168 * @return all non-abstract implementations of the feature type 169 */ 170 public FeatureType[] getSubstitutions( FeatureType featureType ) { 171 FeatureType[] substitutions = new FeatureType[0]; 172 List<FeatureType> featureTypeList = this.substitutionMap.get( featureType ); 173 if ( featureTypeList != null ) { 174 substitutions = featureTypeList.toArray( new FeatureType[featureTypeList.size()] ); 175 } 176 return substitutions; 177 } 178 179 /** 180 * Returns whether the specified feature type is a valid substitution for the other specified feature type 181 * (according to the schema). 182 * 183 * @param ft 184 * @param substitution 185 * @return true, if it is valid substitution, false otherwise 186 */ 187 public boolean isValidSubstitution( FeatureType ft, FeatureType substitution ) { 188 FeatureType[] substitutions = getSubstitutions( ft ); 189 for ( int i = 0; i < substitutions.length; i++ ) { 190 if ( substitutions[i].getName().equals( substitution.getName() ) ) { 191 return true; 192 } 193 } 194 return false; 195 } 196 197 /** 198 * Returns all types (abstract or concrete) that are substitutable by the given type. 199 * 200 * TODO implement this a better way 201 * 202 * @param substitution 203 * @return all types that are substitutable by <code>substitution</code> 204 */ 205 public Set<FeatureType> getSubstitutables( FeatureType substitution ) { 206 207 Set<FeatureType> ftSet = new HashSet<FeatureType>(); 208 FeatureType[] allFts = getFeatureTypes(); 209 for ( FeatureType ft : allFts ) { 210 if ( isValidSubstitution( ft, substitution ) ) { 211 ftSet.add( ft ); 212 } 213 } 214 return ftSet; 215 } 216 217 /** 218 * Initializes the internal feature type map which is used to lookup feature types by name. 219 * 220 * @param elementDeclarations 221 * element declarations to process, only element declarations that are substitutable for "gml:_Feature" 222 * are considered 223 * @throws XMLParsingException 224 * @throws UnknownCRSException 225 */ 226 protected void buildFeatureTypeMap( ElementDeclaration[] elementDeclarations ) 227 throws XMLParsingException, UnknownCRSException { 228 for ( int i = 0; i < elementDeclarations.length; i++ ) { 229 LOG.logDebug( "Is element '" + elementDeclarations[i].getName() + "' a feature type definition?" ); 230 if ( elementDeclarations[i].isSubstitutionFor( ABSTRACT_FEATURE ) ) { 231 LOG.logDebug( "Yes." ); 232 FeatureType featureType = buildFeatureType( elementDeclarations[i] ); 233 featureTypeMap.put( featureType.getName(), featureType ); 234 } else { 235 LOG.logDebug( "No." ); 236 } 237 } 238 } 239 240 /** 241 * Initializes the internal feature type substitution map which is used to lookup substitutions for feature types. 242 * <p> 243 * NOTE: As this method relies on the feature type map, #initializeFeatureTypeMap(ElementDeclaration[]) must have 244 * been executed before. 245 * 246 * @see #buildFeatureTypeMap(ElementDeclaration[]) 247 * 248 * @param elementDeclarations 249 * element declarations of the feature types to process 250 */ 251 protected void buildSubstitutionMap( ElementDeclaration[] elementDeclarations ) { 252 Iterator iter = featureTypeMap.values().iterator(); 253 while ( iter.hasNext() ) { 254 FeatureType featureType = (FeatureType) iter.next(); 255 List<FeatureType> substitutionList = new ArrayList<FeatureType>(); 256 LOG.logDebug( "Collecting possible substitutions for feature type '" + featureType.getName() + "'." ); 257 for ( int i = 0; i < elementDeclarations.length; i++ ) { 258 if ( elementDeclarations[i].isAbstract() ) { 259 LOG.logDebug( "Skipping '" + elementDeclarations[i].getName() + "' as it is abstract." ); 260 } else if ( elementDeclarations[i].isSubstitutionFor( featureType.getName() ) ) { 261 LOG.logDebug( "Feature type '" + elementDeclarations[i].getName() 262 + "' is a concrete substitution for feature type '" + featureType.getName() + "'." ); 263 FeatureType substitution = this.featureTypeMap.get( elementDeclarations[i].getName() ); 264 substitutionList.add( substitution ); 265 } 266 } 267 this.substitutionMap.put( featureType, substitutionList ); 268 } 269 } 270 271 @SuppressWarnings("unused") 272 protected FeatureType buildFeatureType( ElementDeclaration element ) 273 throws XMLParsingException, UnknownCRSException { 274 LOG.logDebug( "Building feature type from element declaration '" + element.getName() + "'..." ); 275 QualifiedName name = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() ); 276 ComplexTypeDeclaration complexType = (ComplexTypeDeclaration) element.getType().getTypeDeclaration(); 277 ElementDeclaration[] subElements = complexType.getElements(); 278 PropertyType[] properties = new PropertyType[subElements.length]; 279 for ( int i = 0; i < properties.length; i++ ) { 280 properties[i] = buildPropertyType( subElements[i] ); 281 } 282 return FeatureFactory.createFeatureType( name, element.isAbstract(), properties ); 283 } 284 285 protected PropertyType buildPropertyType( ElementDeclaration element ) 286 throws XMLSchemaException { 287 AbstractPropertyType propertyType = null; 288 QualifiedName propertyName = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() ); 289 QualifiedName typeName = element.getType().getName(); 290 int type = determinePropertyType( element ); 291 if ( typeName == null ) { 292 throw new XMLSchemaException( "No type defined for the property '" + propertyName 293 + "'. No inline definitions supported." ); 294 } 295 if ( typeName.isInNamespace( XSDNS ) ) { 296 propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(), 297 element.getMaxOccurs() ); 298 } else { 299 switch ( type ) { 300 case Types.FEATURE: { 301 propertyType = FeatureFactory.createFeaturePropertyType( propertyName, element.getMinOccurs(), 302 element.getMaxOccurs() ); 303 break; 304 } 305 case Types.GEOMETRY: { 306 propertyType = FeatureFactory.createGeometryPropertyType( propertyName, typeName, 307 element.getMinOccurs(), 308 element.getMaxOccurs() ); 309 break; 310 } 311 default: { 312 // hack to make extended simple types work... 313 propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(), 314 element.getMaxOccurs() ); 315 // throw new XMLSchemaException( "Unexpected type '" 316 // + type + "' in buildPropertyType()." ); 317 } 318 } 319 } 320 return propertyType; 321 } 322 323 /** 324 * Heuristic method that tries to determine the type of GML property that is defined in an XSD element declaration. 325 * 326 * @param element 327 * <code>ElementDeclaration</code> that is a GML property definition 328 * @return type code from <code>Types</code> 329 * @throws UndefinedXSDTypeException 330 * 331 * @see Types 332 */ 333 protected final int determinePropertyType( ElementDeclaration element ) 334 throws UndefinedXSDTypeException { 335 QualifiedName typeName = element.getType().getName(); 336 LOG.logDebug( "Determining property type code for property type='" + typeName + "'..." ); 337 int type = Types.FEATURE; 338 if ( element.getType().isAnonymous() ) { 339 LOG.logDebug( "Inline declaration. Assuming generic GML feature of some kind." ); 340 } else if ( typeName.isInNamespace( XSDNS ) ) { 341 LOG.logDebug( "Must be a basic XSD type." ); 342 try { 343 type = Types.getJavaTypeForXSDType( typeName.getLocalName() ); 344 } catch ( UnknownTypeException e ) { 345 throw new UndefinedXSDTypeException( e.getMessage(), e ); 346 } 347 } else if ( typeName.isInNamespace( GMLNS ) ) { 348 LOG.logDebug( "Maybe a geometry property type?" ); 349 try { 350 type = Types.getJavaTypeForGMLType( typeName.getLocalName() ); 351 LOG.logDebug( "Yes." ); 352 } catch ( UnknownTypeException e ) { 353 LOG.logDebug( "No. Must be a generic GML feature of some kind." ); 354 } 355 } else { 356 LOG.logDebug( "Should be a primitive type in our own namespace." ); 357 if ( !typeName.isInNamespace( getTargetNamespace() ) ) { 358 throw new UndefinedXSDTypeException( "Type '" + typeName 359 + "' cannot be resolved (not in a supported namespace)." ); 360 } 361 SimpleTypeDeclaration simpleType = getSimpleTypeDeclaration( typeName ); 362 if ( simpleType == null ) { 363 throw new UndefinedXSDTypeException( "Simple type '" + typeName + "' cannot be resolved." ); 364 } 365 typeName = simpleType.getRestrictionBaseType().getName(); 366 LOG.logDebug( "Simple base type: '" + typeName + "'. Must be a basic XSD Type." ); 367 try { 368 type = Types.getJavaTypeForXSDType( typeName.getLocalName() ); 369 } catch ( UnknownTypeException e ) { 370 throw new UndefinedXSDTypeException( e ); 371 } 372 } 373 return type; 374 } 375 376 /** 377 * Returns a string representation of the object. 378 * 379 * @return a string representation of the object 380 */ 381 @Override 382 public String toString() { 383 384 Map<FeatureType, List<FeatureType>> substitutesMap = buildSubstitutesMap(); 385 386 StringBuffer sb = new StringBuffer( "GML schema targetNamespace='" ); 387 sb.append( getTargetNamespace() ); 388 sb.append( "'\n" ); 389 sb.append( "\n*** " ); 390 sb.append( featureTypeMap.size() ); 391 sb.append( " feature type declarations ***\n" ); 392 Iterator featureTypeIter = featureTypeMap.values().iterator(); 393 while ( featureTypeIter.hasNext() ) { 394 FeatureType featureType = (FeatureType) featureTypeIter.next(); 395 sb.append( featureTypeToString( featureType, substitutesMap ) ); 396 if ( featureTypeIter.hasNext() ) { 397 sb.append( "\n\n" ); 398 } 399 } 400 return sb.toString(); 401 } 402 403 private Map<FeatureType, List<FeatureType>> buildSubstitutesMap() { 404 405 Map<FeatureType, List<FeatureType>> substitutesMap = new HashMap<FeatureType, List<FeatureType>>(); 406 407 for ( FeatureType ft : getFeatureTypes() ) { 408 List<FeatureType> substitutesList = new ArrayList<FeatureType>(); 409 for ( FeatureType substitution : getFeatureTypes() ) { 410 if ( isValidSubstitution( substitution, ft ) ) { 411 substitutesList.add( substitution ); 412 } 413 } 414 substitutesMap.put( ft, substitutesList ); 415 } 416 return substitutesMap; 417 } 418 419 private String featureTypeToString( FeatureType ft, Map<FeatureType, List<FeatureType>> substitutesMap ) { 420 StringBuffer sb = new StringBuffer( "- " ); 421 if ( ft.isAbstract() ) { 422 sb.append( "(abstract) " ); 423 } 424 sb.append( "Feature type '" ); 425 sb.append( ft.getName() ); 426 sb.append( "'\n" ); 427 428 FeatureType[] substFTs = getSubstitutions( ft ); 429 if ( substFTs.length > 0 ) { 430 sb.append( " is implemented by: " ); 431 for ( int i = 0; i < substFTs.length; i++ ) { 432 sb.append( "'" ); 433 sb.append( substFTs[i].getName().getLocalName() ); 434 if ( substFTs[i].isAbstract() ) { 435 sb.append( " (abstract)" ); 436 } 437 sb.append( "'" ); 438 if ( i != substFTs.length - 1 ) { 439 sb.append( "," ); 440 } else { 441 sb.append( "\n" ); 442 } 443 } 444 } else { 445 sb.append( " has no concrete implementations?!\n" ); 446 } 447 448 List<FeatureType> substitutesList = substitutesMap.get( ft ); 449 sb.append( " substitutes : " ); 450 for ( int i = 0; i < substitutesList.size(); i++ ) { 451 sb.append( "'" ); 452 sb.append( substitutesList.get( i ).getName().getLocalName() ); 453 if ( substitutesList.get( i ).isAbstract() ) { 454 sb.append( " (abstract)" ); 455 } 456 sb.append( "'" ); 457 if ( i != substitutesList.size() - 1 ) { 458 sb.append( "," ); 459 } 460 } 461 sb.append( "\n" ); 462 463 PropertyType[] properties = ft.getProperties(); 464 for ( int i = 0; i < properties.length; i++ ) { 465 PropertyType pt = properties[i]; 466 sb.append( " + '" ); 467 sb.append( pt.getName() ); 468 if ( pt instanceof ComplexPropertyType ) { 469 sb.append( "', Type: '" ); 470 sb.append( ( (ComplexPropertyType) pt ).getTypeName() ); 471 } 472 sb.append( "', SQLType: " ); 473 try { 474 sb.append( Types.getTypeNameForSQLTypeCode( pt.getType() ) ); 475 } catch ( UnknownTypeException e ) { 476 sb.append( "unknown" ); 477 } 478 sb.append( ", min: " ); 479 sb.append( pt.getMinOccurs() ); 480 sb.append( ", max: " ); 481 sb.append( pt.getMaxOccurs() ); 482 if ( i != properties.length - 1 ) { 483 sb.append( "\n" ); 484 } 485 } 486 return sb.toString(); 487 } 488 }