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 }