//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/schema/MappedGMLSchema.java,v 1.43 2006/12/04 18:22:14 mschneider Exp $ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstraße 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.io.datastore.schema; import java.net.URI; import java.util.Iterator; import java.util.Properties; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.xml.XMLParsingException; import org.deegree.framework.xml.schema.ComplexTypeDeclaration; import org.deegree.framework.xml.schema.ElementDeclaration; import org.deegree.framework.xml.schema.SimpleTypeDeclaration; import org.deegree.framework.xml.schema.XMLSchemaException; import org.deegree.io.datastore.Datastore; import org.deegree.io.datastore.DatastoreConfiguration; import org.deegree.io.datastore.DatastoreException; import org.deegree.io.datastore.DatastoreRegistry; import org.deegree.io.datastore.idgenerator.IdGenerator; import org.deegree.io.datastore.schema.MappedGMLId.IDPART_INFO; import org.deegree.io.datastore.schema.content.MappingField; import org.deegree.io.datastore.schema.content.MappingGeometryField; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.CoordinateSystem; import org.deegree.model.crs.UnknownCRSException; import org.deegree.model.feature.schema.AbstractPropertyType; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.GMLSchema; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.feature.schema.UndefinedFeatureTypeException; import org.deegree.ogcbase.CommonNamespaces; import org.w3c.dom.Element; /** * Represents a GML Application Schema document which is annotated with mapping (persistence) * information. * * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> * @author last edited by: $Author: mschneider $ * * @version $Revision: 1.43 $, $Date: 2006/12/04 18:22:14 $ */ public class MappedGMLSchema extends GMLSchema { private final static ILogger LOG = LoggerFactory.getLogger( MappedGMLSchema.class ); private static URI XSDNS = CommonNamespaces.XSNS; private MappedGMLSchemaDocument doc; private Datastore datastore; private boolean suppressXLinkOutput; private String namespacePrefix; private URI defaultSRS; private CoordinateSystem defaultCS; // TODO remove this hack (which is used to mark the first feature type as visible by default) private boolean firstFeatureType = true; /** * Creates a new <code>MappedGMLSchema</code> instance from the given parameters. * * @param targetNamespace * @param simpleTypes * @param complexTypes * @param elementDeclarations * @param namespacePrefix * @param defaultSRS * @param backendConfiguration * @param suppressXLinkOutput * @param doc * @throws UnknownCRSException * @throws XMLSchemaException */ MappedGMLSchema( URI targetNamespace, SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes, ElementDeclaration[] elementDeclarations, String namespacePrefix, URI defaultSRS, DatastoreConfiguration backendConfiguration, boolean suppressXLinkOutput, MappedGMLSchemaDocument doc ) throws XMLParsingException, UnknownCRSException { super( elementDeclarations, targetNamespace, simpleTypes, complexTypes ); this.doc = doc; this.namespacePrefix = namespacePrefix; this.defaultSRS = defaultSRS; this.defaultCS = CRSFactory.create( defaultSRS.toString() ); this.datastore = registerDatastore( backendConfiguration ); this.suppressXLinkOutput = suppressXLinkOutput; buildFeatureTypeMap( elementDeclarations ); buildSubstitutionMap( elementDeclarations ); resolveFeatureTypeReferences(); resolveTargetTables(); checkIdentityPartConsistency(); try { this.datastore.bindSchema( this ); } catch ( DatastoreException e ) { LOG.logError( e.getMessage(), e ); throw new XMLParsingException( e.getMessage() ); } } /** * Checks for all feature type definitions if it's featureIds 'identityPart' setting is valid: * <ul> * <li>if there is a direct fk from the feature's table to another feature table, * 'identityPart' must be true</li> * <li>if there is no explicit setting for the feature type, the implied setting is used, * otherwise it is checked for validity</li> * </ul> * * @throws XMLSchemaException */ private void checkIdentityPartConsistency() throws XMLSchemaException { for ( FeatureType ft : this.featureTypeMap.values() ) { MappedFeatureType mft = (MappedFeatureType) ft; PropertyType[] properties = mft.getProperties(); for ( int i = 0; i < properties.length; i++ ) { MappedPropertyType property = (MappedPropertyType) properties[i]; if ( property instanceof MappedFeaturePropertyType ) { MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) property; TableRelation[] relations = featurePT.getTableRelations(); if ( relations.length == 1 ) { if ( relations[0].getFKInfo() == TableRelation.FK_INFO.fkIsToField ) { MappedFeatureType targetFT = featurePT.getFeatureTypeReference().getFeatureType(); MappedGMLId id = targetFT.getGMLId(); if ( id.getIdPartInfo() == IDPART_INFO.noIDInfo ) { String msg = "FeatureId for feature type '" + targetFT.getName() + "' has to be part of the feature's identity - feature table " + "is a property of feature type '" + mft.getName() + "' and stores a fk."; LOG.logInfo( msg ); } else if ( id.getIdPartInfo() == IDPART_INFO.notIDPart ) { String msg = "Invalid schema annotation: " + "FeatureId for feature type '" + targetFT.getName() + "' has to be part of the feature's identity - feature table " + "is a property of feature type '" + mft.getName() + "' and stores a fk. Set 'identityPart' to true for " + "feature type '" + targetFT.getName() + "'."; throw new XMLSchemaException( msg ); } id.setIdentityPart( true ); } } } } } } /** * Retrieves a <code>Datastore</code> instance for the given configuration. * <p> * If a datastore with exactly the same configuration exists, the existing instance is returned. * * @param backendConfiguration * @throws XMLSchemaException */ private Datastore registerDatastore( DatastoreConfiguration backendConfiguration ) throws XMLSchemaException { Datastore datastore = DatastoreRegistry.getDatastore( backendConfiguration ); if ( datastore == null ) { try { datastore = (Datastore) backendConfiguration.getDatastoreClass().newInstance(); datastore.configure( backendConfiguration ); } catch ( DatastoreException e ) { String msg = "Error configuring datastore with configuration '" + backendConfiguration + "'."; LOG.logError( msg, e ); throw new XMLSchemaException( msg, e ); } catch ( Exception e ) { String msg = "Error instantiating datastore for class '" + backendConfiguration.getDatastoreClass() + "'."; LOG.logError( msg, e ); throw new XMLSchemaException( msg, e ); } try { DatastoreRegistry.registerDatastore( datastore ); } catch ( DatastoreException e ) { String msg = "Error registering datastore with configuration '" + backendConfiguration + "'."; LOG.logError( msg, e ); throw new XMLSchemaException( msg, e ); } } return datastore; } /** * Returns the underlying GML Application Schema document. * * @return the underlying GML Application Schema document */ public MappedGMLSchemaDocument getDocument() { return this.doc; } /** * Returns the {@link Datastore} instance that handles this schema. * * @return the Datastore instance that handles this schema */ public Datastore getDatastore() { return this.datastore; } /** * Returns whether GML output (of the associated datastore) will not use any XLinks. * * @return true, if the GML output will not use XLinks, false otherwise */ public boolean suppressXLinkOutput() { return this.suppressXLinkOutput; } /** * Returns the default SRS for all geometry properties in the schema. * * @return the default SRS for all geometry properties in the schema */ public URI getDefaultSRS() { return this.defaultSRS; } /** * Returns the default {@link CoordinateSystem} for all geometry properties in the schema. * * @return the default CoordinateSystem for all geometry properties in the schema */ public CoordinateSystem getDefaultCS() { return this.defaultCS; } /** * Builds a {@link MappedFeatureType} from the given element declaration. * * @param element * @return feature type with persistence information * @throws XMLParsingException * @throws UnknownCRSException */ @Override protected MappedFeatureType buildFeatureType( ElementDeclaration element ) throws XMLParsingException, UnknownCRSException { LOG.logDebug( "Building (mapped) feature type from element declaration '" + element.getName() + "'..." ); int visibleCode = -1; boolean isVisible = false; boolean isUpdatable = false; boolean isDeletable = false; boolean isInsertable = false; QualifiedName name = new QualifiedName( this.namespacePrefix, element.getName().getLocalName(), getTargetNamespace() ); MappedComplexTypeDeclaration complexType = (MappedComplexTypeDeclaration) element.getType().getTypeDeclaration(); // extract mapping information from element annotation Element annotationElement = ( (MappedElementDeclaration) element ).getAnnotation(); MappedGMLId gmlId = null; String table = name.getLocalName().toLowerCase(); // use complexType annotation, if no element annotation present if ( annotationElement == null ) { annotationElement = complexType.getAnnotation(); } // neither element nor complexType annotation, then use default mapping if ( annotationElement == null ) { LOG.logInfo( "Declaration of feature type '" + name + "' has no mapping information (annotation element). Defaulting to " + "table name '" + table + "' and gmlId field 'fid' (not identity part)." ); MappingField[] idFields = new MappingField[] { new MappingField( table, "fid", Types.VARCHAR ) }; IdGenerator idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); gmlId = new MappedGMLId( name.getLocalName().toUpperCase(), "_", idFields, idGenerator, IDPART_INFO.noIDInfo ); } else { gmlId = doc.extractGMLId( annotationElement, table ); table = gmlId.getIdFields()[0].getTable(); try { visibleCode = doc.parseVisible( annotationElement ); isUpdatable = doc.parseIsUpdatable( annotationElement ); isDeletable = doc.parseIsDeletable( annotationElement ); isInsertable = doc.parseIsInsertable( annotationElement ); } catch ( XMLParsingException e ) { throw new XMLSchemaException( e ); } } ElementDeclaration[] subElements = complexType.getElements(); PropertyType[] properties = new PropertyType[subElements.length]; for ( int i = 0; i < subElements.length; i++ ) { MappedElementDeclaration subElement = (MappedElementDeclaration) subElements[i]; properties[i] = buildPropertyType( subElement, table ); } // default visibility for first feature type is true, for all others it's false if ( this.firstFeatureType ) { isVisible = true; if ( visibleCode == 0 ) { isVisible = false; } this.firstFeatureType = false; } else { if ( visibleCode == 1 ) { isVisible = true; } } return new MappedFeatureType( name, element.isAbstract(), properties, table, gmlId, this, isVisible, isUpdatable, isDeletable, isInsertable ); } protected PropertyType buildPropertyType( MappedElementDeclaration element, String table ) throws XMLParsingException, UnknownCRSException { AbstractPropertyType propertyType; QualifiedName propertyName = new QualifiedName( this.namespacePrefix, element.getName().getLocalName(), getTargetNamespace() ); int minOccurs = element.getMinOccurs(); int maxOccurs = element.getMaxOccurs(); QualifiedName typeName = element.getType().getName(); LOG.logDebug( "Building (mapped) property type from element declaration '" + propertyName + "', type='" + typeName + "'..." ); int type = determinePropertyType( element ); // extract mapping annotation Element annotationElement = element.getAnnotation(); // get identityPart information from annotation int identityCode = -1; if ( annotationElement != null ) { identityCode = doc.parseIdentityPart( annotationElement ); } if ( typeName.isInNamespace( XSDNS ) ) { // simple property (basic xsd type) if ( annotationElement == null ) { LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." ); String field = propertyName.getLocalName().toLowerCase(); int typeCode = getDefaultSQLTypeForXSDType( typeName ); MappingField mappingField = new MappingField( table, field, typeCode ); propertyType = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, true, new TableRelation[0], mappingField ); } else { LOG.logDebug( "Parsing mapping information for simple property type." ); boolean isIdentityPart = identityCode == 0 ? false : true; propertyType = doc.parseMappedSimplePropertyType( annotationElement, propertyName, type, minOccurs, maxOccurs, isIdentityPart, table ); } } else { switch ( type ) { case Types.GEOMETRY: { // geometry property if ( annotationElement == null ) { LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." ); String field = propertyName.getLocalName().toLowerCase(); MappingGeometryField mappingField = new MappingGeometryField( table, field, Types.OTHER, -1 ); propertyType = new MappedGeometryPropertyType( propertyName, typeName, type, minOccurs, maxOccurs, false, this.defaultSRS, new TableRelation[0], mappingField ); } else { LOG.logDebug( "Parsing mapping information for geometry property type." ); boolean isIdentityPart = identityCode == 1 ? true : false; propertyType = doc.parseMappedGeometryPropertyType( annotationElement, propertyName, typeName, type, minOccurs, maxOccurs, isIdentityPart, table ); } break; } case Types.FEATURE: { // feature property if ( annotationElement == null ) { String msg = "Declaration of property type '" + propertyName + "' has no mapping information (annotation element missing)."; throw new XMLSchemaException( msg ); } LOG.logDebug( "Parsing mapping information for feature property type." ); boolean isIdentityPart = identityCode == 0 ? false : true; propertyType = doc.parseMappedFeaturePropertyType( annotationElement, propertyName, minOccurs, maxOccurs, isIdentityPart, table ); break; } default: { // no known namespace -> assume simple property with user defined simple type // TODO check for inherited types if ( annotationElement == null ) { LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." ); String field = propertyName.getLocalName().toLowerCase(); int typeCode = getDefaultSQLTypeForXSDType( typeName ); MappingField mappingField = new MappingField( table, field, typeCode ); propertyType = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, true, new TableRelation[0], mappingField ); } else { LOG.logDebug( "Parsing mapping information for simple property type." ); boolean isIdentityPart = identityCode == 0 ? false : true; propertyType = doc.parseMappedSimplePropertyType( annotationElement, propertyName, type, minOccurs, maxOccurs, isIdentityPart, table ); } } } } return propertyType; } /** * @throws XMLSchemaException */ private void resolveTargetTables() throws XMLSchemaException { LOG.logDebug( "Resolving unspecified (null) table references for all FeaturePropertyTypes." ); Iterator iter = featureTypeMap.values().iterator(); while ( iter.hasNext() ) { resolveTargetTables( (MappedFeatureType) iter.next() ); } } private void resolveTargetTables( MappedFeatureType type ) throws XMLSchemaException { PropertyType[] properties = type.getProperties(); for ( int i = 0; i < properties.length; i++ ) { MappedPropertyType property = (MappedPropertyType) properties[i]; if ( property instanceof MappedFeaturePropertyType ) { resolveTargetTables( (MappedFeaturePropertyType) property ); } } } private void resolveTargetTables( MappedFeaturePropertyType featurePT ) throws XMLSchemaException { MappedFeatureType targetFeatureType = featurePT.getFeatureTypeReference().getFeatureType(); if ( !targetFeatureType.isAbstract() ) { TableRelation[] tableRelations = featurePT.getTableRelations(); if ( tableRelations.length == 0 ) { String msg = "Invalid feature property mapping '" + featurePT.getName() + ": no relation elements - feature properties cannot be embedded in " + "feature tables directly, but must use key relations to reference " + "subfeatures."; LOG.logError( msg ); throw new XMLSchemaException( msg ); } TableRelation lastRelation = tableRelations[tableRelations.length - 1]; MappingField[] targetFields = lastRelation.getToFields(); for ( int i = 0; i < targetFields.length; i++ ) { String table = targetFields[i].getTable(); if ( table != null ) { if ( !targetFeatureType.getTable().equals( table ) ) { String msg = "Invalid feature property mapping: type '" + targetFeatureType.getName() + "' is bound to table '" + targetFeatureType.getTable() + "', but last table relation specifies table '" + table + "'."; LOG.logError( msg ); throw new XMLSchemaException( msg ); } } targetFields[i].setTable( targetFeatureType.getTable() ); } } } private void resolveFeatureTypeReferences() throws UndefinedFeatureTypeException { LOG.logDebug( "Resolving (mapped) FeatureType references for namespace '" + getTargetNamespace() + "'." ); Iterator iter = featureTypeMap.values().iterator(); while ( iter.hasNext() ) { resolveFeatureTypeReferences( (MappedFeatureType) iter.next() ); } } private void resolveFeatureTypeReferences( MappedFeatureType featureType ) throws UndefinedFeatureTypeException { LOG.logDebug( "Resolving (mapped) FeatureType references in definition of FeatureType '" + featureType.getName() + "'." ); PropertyType[] properties = featureType.getProperties(); for ( int i = 0; i < properties.length; i++ ) { if ( properties[i] instanceof MappedFeaturePropertyType ) { MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) properties[i]; resolveFeatureTypeReferences( featurePT.getFeatureTypeReference() ); } } } private void resolveFeatureTypeReferences( MappedFeatureTypeReference reference ) throws UndefinedFeatureTypeException { LOG.logDebug( "Resolving (mapped) FeatureType references to FeatureType '" + reference.getName() + "'." ); if ( reference.isResolved() ) { LOG.logDebug( "Already resolved." ); } else { MappedFeatureType featureType = (MappedFeatureType) getFeatureType( reference.getName() ); if ( featureType == null ) { String msg = "Reference to feature type '" + reference.getName() + "' in mapping annotation can not be resolved."; LOG.logDebug( msg ); throw new UndefinedFeatureTypeException( msg ); } reference.resolve( featureType ); resolveFeatureTypeReferences( featureType ); } } // TODO: implement this private int getDefaultSQLTypeForXSDType( @SuppressWarnings("unused") QualifiedName xsdTypeName ) { return -1; } @Override public String toString() { StringBuffer sb = new StringBuffer( "GML schema targetNamespace='" ); sb.append( getTargetNamespace() ); sb.append( "'\n" ); sb.append( "\n*** " ); sb.append( featureTypeMap.size() ); sb.append( " feature type declarations ***\n" ); Iterator featureTypeIter = featureTypeMap.values().iterator(); while ( featureTypeIter.hasNext() ) { MappedFeatureType featureType = (MappedFeatureType) featureTypeIter.next(); sb.append( featureTypeToString( featureType ) ); if ( featureTypeIter.hasNext() ) { sb.append( "\n\n" ); } } return sb.toString(); } private String featureTypeToString( MappedFeatureType featureType ) { StringBuffer sb = new StringBuffer( "- " ); if ( featureType.isAbstract() ) { sb.append( "(abstract) " ); } sb.append( "Feature type '" ); sb.append( featureType.getName() ); sb.append( "' -> TABLE: '" ); sb.append( featureType.getTable() + "'" ); if ( featureType.isUpdatable() ) { sb.append( " updatable" ); } if ( featureType.isDeletable() ) { sb.append( " deletable" ); } if ( featureType.isInsertable() ) { sb.append( " insertable" ); } sb.append( '\n' ); PropertyType[] properties = featureType.getProperties(); for ( int i = 0; i < properties.length; i++ ) { sb.append( " + '" ); sb.append( properties[i].getName() ); sb.append( "', type: " ); sb.append( properties[i].getType() ); sb.append( ", minOccurs: " ); sb.append( properties[i].getMinOccurs() ); sb.append( ", maxOccurs: " ); sb.append( properties[i].getMaxOccurs() ); sb.append( " -> " ); //sb.append( ( (MappedPropertyType) properties[i] ).getContents()[0] ); if ( i != properties.length - 1 ) { sb.append( "\n" ); } } return sb.toString(); } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: MappedGMLSchema.java,v $ Revision 1.43 2006/12/04 18:22:14 mschneider Added error message in case no relation elements for a feature property type are present. Revision 1.42 2006/11/29 17:00:40 mschneider Changed internal SRS to integer. Revision 1.41 2006/11/27 09:07:51 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.40 2006/08/29 15:52:12 mschneider Fixed warnings. Revision 1.39 2006/08/28 16:43:25 mschneider Fixed @Override annotations. Revision 1.38 2006/08/23 12:18:02 mschneider Javadoc fixes. Revision 1.37 2006/08/22 18:14:42 mschneider Refactored due to cleanup of org.deegree.io.datastore.schema package. Revision 1.36 2006/08/21 16:42:36 mschneider Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package. Revision 1.35 2006/08/21 15:43:20 mschneider Cleanup. Added "content" subpackage. Removed (completely unused and outdated) FeatureArrayPropertyType. Revision 1.34 2006/07/10 20:54:00 mschneider Optimized formatting. Revision 1.33 2006/05/21 19:06:46 poth *** empty log message *** Revision 1.32 2006/05/01 20:15:26 poth *** empty log message *** Revision 1.31 2006/04/19 18:24:03 mschneider Added identity part for feature ids. Revision 1.30 2006/04/18 12:45:06 mschneider Added sanity checks to prevent common misconfigurations. Revision 1.29 2006/04/06 20:25:23 poth *** empty log message *** Revision 1.28 2006/04/04 20:39:41 poth *** empty log message *** Revision 1.27 2006/04/04 10:28:31 mschneider Added identityPart handling. Used for equality definition of feature types. Revision 1.26 2006/03/30 21:20:24 poth *** empty log message *** Revision 1.25 2006/03/02 18:03:51 poth *** empty log message *** Revision 1.24 2006/02/22 00:21:44 mschneider Renamed AbstractDatastore to Datastore. Revision 1.23 2006/02/17 14:34:21 mschneider Changed due to refactoring of GMLIdGenerator. Revision 1.22 2006/02/09 10:36:35 mschneider Fixed handling of "deegreewfs:visible" annotation. Revision 1.21 2006/02/08 17:42:10 mschneider Added handling of suppressXLinkOutput parameter. ********************************************************************** */