//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/schema/MappedGMLSchemaDocument.java,v 1.44 2006/11/29 17:00:40 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.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.deegree.datatypes.QualifiedName;
import org.deegree.datatypes.Types;
import org.deegree.datatypes.UnknownTypeException;
import org.deegree.framework.xml.XMLParsingException;
import org.deegree.framework.xml.XMLTools;
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.TypeDeclaration;
import org.deegree.framework.xml.schema.TypeReference;
import org.deegree.framework.xml.schema.XMLSchemaException;
import org.deegree.i18n.Messages;
import org.deegree.io.IODocument;
import org.deegree.io.JDBCConnection;
import org.deegree.io.datastore.DatastoreConfiguration;
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.TableRelation.FK_INFO;
import org.deegree.io.datastore.schema.content.ConstantContent;
import org.deegree.io.datastore.schema.content.FieldContent;
import org.deegree.io.datastore.schema.content.FunctionParam;
import org.deegree.io.datastore.schema.content.MappingField;
import org.deegree.io.datastore.schema.content.MappingGeometryField;
import org.deegree.io.datastore.schema.content.SQLFunctionCall;
import org.deegree.io.datastore.schema.content.SimpleContent;
import org.deegree.io.datastore.schema.content.SpecialContent;
import org.deegree.io.datastore.shape.ShapeDatastoreConfiguration;
import org.deegree.io.datastore.sql.SQLDatastoreConfiguration;
import org.deegree.model.crs.UnknownCRSException;
import org.deegree.model.feature.schema.GMLSchemaDocument;
import org.deegree.ogcbase.CommonNamespaces;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Parser for GML Schema documents which are 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.44 $, $Date: 2006/11/29 17:00:40 $
*/
public class MappedGMLSchemaDocument extends GMLSchemaDocument {
private static final long serialVersionUID = 8293629056821438839L;
private String namespacePrefix;
private URI defaultSRS;
private DatastoreConfiguration datastoreConfiguration;
private boolean suppressXLinkOutput;
/**
* Returns the class representation of the underlying mapped GML Schema document.
*
* @return the class representation of the underlying mapped GML Schema document
* @throws XMLParsingException
* @throws XMLSchemaException
* @throws UnknownCRSException
*/
public MappedGMLSchema parseMappedGMLSchema()
throws XMLParsingException, XMLSchemaException, UnknownCRSException {
extractGlobalAnnotations();
SimpleTypeDeclaration[] simpleTypes = extractSimpleTypeDeclarations();
ComplexTypeDeclaration[] complexTypes = extractComplexTypeDeclarations();
ElementDeclaration[] elementDeclarations = extractElementDeclarations();
return new MappedGMLSchema( getTargetNamespace(), simpleTypes, complexTypes,
elementDeclarations, this.namespacePrefix, this.defaultSRS,
this.datastoreConfiguration, this.suppressXLinkOutput, this );
}
/**
* Parses the given <code>Element</code> as an annotated 'xs:element' declaration (with
* mapping information).
*
* @param element
* 'xs:element' declaration to be parsed
* @return object representation of the declaration
* @throws XMLParsingException
* if the document is not a valid XML Schema document or does not match the
* limitations of this class
*/
@Override
protected MappedElementDeclaration parseElementDeclaration( Element element )
throws XMLParsingException {
QualifiedName name = new QualifiedName( XMLTools.getRequiredNodeAsString( element, "@name",
nsContext ),
getTargetNamespace() );
if ( name.getLocalName().length() == 0 ) {
String msg = "Error in schema document. Empty name (\"\") in element declaration "
+ "found.";
throw new XMLSchemaException( msg );
}
boolean isAbstract = XMLTools.getNodeAsBoolean( element, "@abstract", nsContext, false );
TypeReference typeReference = null;
Node typeNode = XMLTools.getNode( element, "@type", nsContext );
if ( typeNode != null ) {
typeReference = new TypeReference( parseQualifiedName( typeNode ) );
} else {
// inline type declaration
Element elem = (Element) XMLTools.getRequiredNode( element,
getFullName( "complexType" ),
nsContext );
TypeDeclaration type = parseComplexTypeDeclaration( elem );
typeReference = new TypeReference( type );
}
int minOccurs = XMLTools.getNodeAsInt( element, "@minOccurs", nsContext, 1 );
int maxOccurs = -1;
String maxOccursString = XMLTools.getNodeAsString( element, "@maxOccurs", nsContext, "1" );
if ( !"unbounded".equals( maxOccursString ) ) {
try {
maxOccurs = Integer.parseInt( maxOccursString );
} catch ( NumberFormatException e ) {
throw new XMLParsingException( "Invalid value ('" + maxOccursString
+ "') in 'maxOccurs' attribute. "
+ "Must be a valid integer value or 'unbounded'." );
}
}
QualifiedName substitutionGroup = null;
Node substitutionGroupNode = XMLTools.getNode( element, "@substitutionGroup", nsContext );
if ( substitutionGroupNode != null ) {
substitutionGroup = parseQualifiedName( substitutionGroupNode );
}
Element annotationElement = (Element) XMLTools.getNode( element,
getFullName( "annotation" ),
nsContext );
return new MappedElementDeclaration( name, isAbstract, typeReference, minOccurs, maxOccurs,
substitutionGroup, annotationElement );
}
/**
* Parses the given <code>Element</code> as an annotated 'xs:complexType' declaration
* (with mapping information).
*
* @param element
* 'xs:complexType' declaration to be parsed
* @return object representation of the declaration
* @throws XMLParsingException
* if the document is not a valid XML Schema document or does not match the
* limitations of this class
*/
@Override
protected MappedComplexTypeDeclaration parseComplexTypeDeclaration( Element element )
throws XMLParsingException {
QualifiedName name = null;
String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null );
if ( localName != null ) {
name = new QualifiedName( localName, getTargetNamespace() );
if ( localName.length() == 0 ) {
String msg = "Error in schema document. Empty name (\"\") for complexType "
+ "declaration found.";
throw new XMLSchemaException( msg );
}
}
List subElementList = null;
TypeReference extensionBase = null;
Node extensionBaseNode = XMLTools.getNode( element, getFullName( "complexContent/" )
+ getFullName( "extension/@base" ),
nsContext );
if ( extensionBaseNode != null ) {
extensionBase = new TypeReference( parseQualifiedName( extensionBaseNode ) );
subElementList = XMLTools.getNodes( element, getFullName( "complexContent/" )
+ getFullName( "extension/" )
+ getFullName( "sequence/" )
+ getFullName( "element" ), nsContext );
} else {
subElementList = XMLTools.getRequiredNodes( element, getFullName( "sequence/" )
+ getFullName( "element" ),
nsContext );
}
ElementDeclaration[] subElements = new ElementDeclaration[subElementList.size()];
for ( int i = 0; i < subElements.length; i++ ) {
Element subElement = (Element) subElementList.get( i );
subElements[i] = parseElementDeclaration( subElement );
}
Element annotationElement = (Element) XMLTools.getNode( element,
getFullName( "annotation" ),
nsContext );
return new MappedComplexTypeDeclaration( name, extensionBase, subElements,
annotationElement );
}
/**
* // TODO support other backend types
*
* @throws XMLParsingException
*/
private void extractGlobalAnnotations()
throws XMLParsingException {
Element appinfoElement = (Element) XMLTools.getRequiredNode( getRootElement(),
"xs:annotation/xs:appinfo",
nsContext );
this.namespacePrefix = XMLTools.getRequiredNodeAsString( appinfoElement,
"deegreewfs:Prefix", nsContext );
String backend = XMLTools.getRequiredNodeAsString( appinfoElement, "deegreewfs:Backend",
nsContext );
this.suppressXLinkOutput = XMLTools.getNodeAsBoolean(
appinfoElement,
"deegreewfs:SuppressXLinkOutput/text()",
nsContext, false );
Class datastoreClass = DatastoreRegistry.getDatastoreClass( backend );
if ( "SHAPE".equals( backend ) ) {
URL file = null;
String fileName = XMLTools.getRequiredNodeAsString( appinfoElement,
"deegreewfs:File/text()", nsContext );
try {
file = resolve( fileName );
} catch ( MalformedURLException e ) {
throw new XMLParsingException( "File '" + fileName + "' is not a valid URL.", e );
}
this.datastoreConfiguration = new ShapeDatastoreConfiguration( backend, datastoreClass,
file );
} else if ( "ORACLE".equals( backend ) || "POSTGIS".equals( backend )
|| "GENERICSQL".equals( backend ) || "SDE".equals( backend ) ) {
// SQL
JDBCConnection jdbcConnection = new IODocument(
(Element) XMLTools.getRequiredNode(
appinfoElement,
"dgjdbc:JDBCConnection",
nsContext ) ).parseJDBCConnection();
this.datastoreConfiguration = new SQLDatastoreConfiguration( backend, datastoreClass,
jdbcConnection );
} else {
this.datastoreConfiguration = new DatastoreConfiguration( backend, datastoreClass );
}
this.defaultSRS = XMLTools.getRequiredNodeAsURI( appinfoElement, "deegreewfs:DefaultSRS",
nsContext );
}
/**
* Extracts the "gml:id" information from the given "xs:annotation" element.
*
* @param annotationElement
* "xs:annotation" element
* @param defaultTableName
* name for table if "deegreewfs:table"-element is missing
* @return "gml:id" information as MappedGMLId
* @throws XMLSchemaException
* if a syntactic or semantic error is found
*/
MappedGMLId extractGMLId( Element annotationElement, String defaultTableName )
throws XMLSchemaException {
MappedGMLId gmlId = null;
try {
String table = XMLTools.getNodeAsString( annotationElement,
"xs:appinfo/deegreewfs:table/text()",
nsContext, defaultTableName );
Element gmlIdElement = (Element) XMLTools.getNode( annotationElement,
"xs:appinfo/deegreewfs:gmlId",
nsContext );
if ( gmlIdElement != null ) {
gmlId = parseGMLIdDefinition( gmlIdElement, table );
} else {
MappingField[] idFields = new MappingField[] { new MappingField( table, "fid",
Types.VARCHAR ) };
IdGenerator idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID,
new Properties() );
gmlId = new MappedGMLId( defaultTableName.toUpperCase(), "_", idFields,
idGenerator, IDPART_INFO.notIDPart );
}
} catch ( XMLParsingException e ) {
throw new XMLSchemaException( e.getMessage(), e );
}
return gmlId;
}
/**
* Extracts the mapping information for a simple property type from the given "xs:annotation"
* element.
*
* @param element
* "xs:annotation" element
* @param propertyName
* name of the property (local part is used as default field name if it is not
* specified explicitly in the "MappingField" element)
* @param type
* @param minOccurs
* @param maxOccurs
* @param isIdentityPart
* @param table
* name of the table that is associated with the feature type that this property
* belongs to
* @return simple property type with persistence information
* @throws XMLSchemaException
* if a syntactic or semantic error is found
*/
MappedSimplePropertyType parseMappedSimplePropertyType( Element element,
QualifiedName propertyName, int type,
int minOccurs, int maxOccurs,
boolean isIdentityPart, String table )
throws XMLSchemaException {
MappedSimplePropertyType pt = null;
try {
Element contentElement = (Element) XMLTools.getRequiredNode(
element,
"xs:appinfo/deegreewfs:Content",
nsContext );
// sanity check (common user error)
Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext );
if ( typeNode != null ) {
String msg = "Content element for simple property type '" + propertyName
+ "' must not contain a type attribute.";
throw new XMLParsingException( msg );
}
// table relations
List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation",
nsContext );
TableRelation[] tableRelations = new TableRelation[relationElements.size()];
String fromTable = table;
for ( int i = 0; i < tableRelations.length; i++ ) {
tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ),
fromTable );
fromTable = tableRelations[i].getToTable();
}
SimpleContent content = null;
// check type of content
Node node = XMLTools.getNode( contentElement, "deegreewfs:MappingField", nsContext );
if ( node != null ) {
String defaultField = propertyName.getLocalName();
content = parseMappingField( (Element) node, defaultField, table );
} else {
node = XMLTools.getNode( contentElement, "deegreewfs:Constant", nsContext );
if ( node != null ) {
content = parseConstantContent( (Element) node );
} else {
node = XMLTools.getNode( contentElement, "deegreewfs:SQLFunctionCall",
nsContext );
if ( node != null ) {
content = parseSQLFunctionCall( (Element) node, table );
}
}
}
pt = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs,
isIdentityPart, tableRelations, content );
} catch ( XMLParsingException e ) {
throw new XMLSchemaException( "Error in definition of simple property '" + propertyName
+ "': " + e.getMessage() );
}
return pt;
}
/**
* Parses the given element as a "deegreewfs:Constant" element.
*
* @param element
* "deegreewfs:Constant" element
* @return java representation of element
* @throws XMLParsingException
*/
ConstantContent parseConstantContent( Element element )
throws XMLParsingException {
String constant = XMLTools.getRequiredNodeAsString( element, "text()", nsContext );
return new ConstantContent( constant );
}
/**
* Parses the given "deegreewfs:SpecialContent" element.
*
* @param element
* "deegreewfs:SpecialContent" element
* @return java representation of element
* @throws XMLParsingException
*/
SpecialContent parseSpecialContent( Element element )
throws XMLParsingException {
SpecialContent content = null;
String variable = XMLTools.getRequiredNodeAsString( element, "text()", nsContext );
try {
content = new SpecialContent( variable );
} catch ( Exception e ) {
String msg = Messages.getMessage( "DATASTORE_PARSING_SPECIAL_CONTENT", e.getMessage() );
throw new XMLParsingException( msg );
}
return content;
}
/**
* Parses the given element as a "deegreewfs:SQLFunctionCall" element.
*
* @param element
* "deegreewfs:SQLFunctionCall" element
* @param table
* @return java representation of element
* @throws XMLParsingException
*/
SQLFunctionCall parseSQLFunctionCall( Element element, String table )
throws XMLParsingException {
String callString = XMLTools.getRequiredNodeAsString( element, "@call", nsContext );
String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext );
int typeCode = -1;
try {
typeCode = Types.getTypeCodeForSQLType( typeName );
} catch ( UnknownTypeException e ) {
throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e );
}
List nl = XMLTools.getNodes( element, "deegreewfs:FunctionParam", nsContext );
List<FunctionParam> functionParams = new ArrayList<FunctionParam>();
Iterator iter = nl.iterator();
while ( iter.hasNext() ) {
Element paramElement = (Element) iter.next();
functionParams.add( parseFunctionParam( paramElement, table ) );
}
// validate variable references
int maxVar = extractMaxVariableNumber( callString );
if ( maxVar > functionParams.size() ) {
String msg = "Error in FunctionCall definition ('" + callString
+ "') - call string uses variable $" + maxVar + ", but only has "
+ functionParams.size() + " FunctionParam elements.";
throw new XMLParsingException( msg );
}
// check SRS for all function params (mapped geometry columns)
int internalSRS = -1;
for ( FunctionParam param : functionParams ) {
if ( param instanceof FieldContent ) {
MappingField mf = ( (FieldContent) param ).getField();
if ( mf instanceof MappingGeometryField ) {
int thisSRS = ( (MappingGeometryField) mf ).getSRS();
if ( internalSRS == -1 ) {
internalSRS = thisSRS;
} else {
if ( internalSRS != thisSRS ) {
String msg = Messages.getMessage(
"DATASTORE_SQL_FUNCTION_CALL_INVALID_SRS",
internalSRS, thisSRS );
throw new XMLParsingException( msg );
}
}
}
}
}
// set SRS for all 'SpecialContent' function params
for ( FunctionParam param : functionParams ) {
if ( param instanceof SpecialContent ) {
( (SpecialContent) param ).setSRS( internalSRS );
}
}
return new SQLFunctionCall( callString, typeCode, functionParams );
}
/**
* Extracts maximum variable numbers ('$i') used in the given call string.
*
* TODO: handle leading zeros
*
* @param callString
* @return maximum variable numbers used in the given call string
*/
private int extractMaxVariableNumber( String callString )
throws XMLParsingException {
int maxVar = 0;
int foundAt = callString.indexOf( '$' );
while ( foundAt != -1 ) {
foundAt++;
String varNumberString = "";
while ( foundAt < callString.length() ) {
char numberChar = callString.charAt( foundAt++ );
if ( numberChar >= '0' && numberChar <= '9' ) {
varNumberString += numberChar;
} else {
break;
}
}
if ( varNumberString.length() == 0 ) {
String msg = "Error in call attribute ('" + callString
+ "') of FunctionCall definition - parameters must be specified by "
+ "the '$' symbol, followed by a positive integer.";
throw new XMLParsingException( msg );
}
try {
int varNo = Integer.parseInt( varNumberString );
if ( varNo > maxVar ) {
maxVar = varNo;
}
if ( varNo == 0 ) {
String msg = "Error in call attribute ('" + callString
+ "') of FunctionCall definition - $0 is not a valid variable "
+ "identifier. Counting of variables starts with 1.";
throw new XMLParsingException( msg );
}
} catch ( NumberFormatException e ) {
assert ( false );
}
// find next '$' symbol
foundAt = callString.indexOf( '$', foundAt );
}
return maxVar;
}
/**
* Parses the given "deegreewfs:FunctionParam" element.
* <p>
* Valid child elements:
* <ul>
* <li>deegreewfs:MappingField</li>
* <li>deegreewfs:ConstantContent</li>
* <li>deegreewfs:SpecialContent</li>
* </ul>
*
* @param element
* "deegreewfs:FunctionParam" element
* @param table
* @return java representation of element
* @throws XMLParsingException
*/
FunctionParam parseFunctionParam( Element element, String table )
throws XMLParsingException {
FunctionParam param = null;
Element childElement = XMLTools.getFirstChildElement( element );
if ( childElement == null
|| !childElement.getNamespaceURI().equals( CommonNamespaces.DEEGREEWFS.toString() ) ) {
String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" );
throw new XMLParsingException( msg );
}
if ( "MappingField".equals( childElement.getLocalName() ) ) {
// table relations
List relationElements = XMLTools.getNodes( element, "deegreewfs:Relation", nsContext );
TableRelation[] tablePath = new TableRelation[relationElements.size()];
String fromTable = table;
for ( int i = 0; i < tablePath.length; i++ ) {
tablePath[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable );
fromTable = tablePath[i].getToTable();
}
// TODO do this a better way
String type = XMLTools.getRequiredNodeAsString( childElement, "@type", nsContext );
MappingField field;
if ( "GEOMETRY".equals( type ) ) {
field = parseGeometryMappingField( childElement, null, fromTable );
} else {
field = parseMappingField( childElement, null, fromTable );
}
param = new FieldContent( field, tablePath );
} else if ( "ConstantContent".equals( childElement.getLocalName() ) ) {
param = parseConstantContent( childElement );
} else if ( "SpecialContent".equals( childElement.getLocalName() ) ) {
param = parseSpecialContent( childElement );
} else {
String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" );
throw new XMLParsingException( msg );
}
return param;
}
/**
* Extracts the mapping information for a geometry property type from the given "xs:annotation"
* element.
*
* @param element
* "xs:annotation" element
* @param propertyName
* name of the property (local part is used as default field name if it is not
* specified explicitly in the "MappingField" element)
* @param type
* @param minOccurs
* @param maxOccurs
* @param isIdentityPart
* @param table
* name of the table that is associated with the feature type that this property
* belongs to
* @return geometry property type with persistence information
* @throws XMLSchemaException
* if a syntactic or semantic error is found
* @throws UnknownCRSException
*/
MappedGeometryPropertyType parseMappedGeometryPropertyType( Element element,
QualifiedName propertyName,
QualifiedName typeName, int type,
int minOccurs, int maxOccurs,
boolean isIdentityPart, String table )
throws XMLSchemaException, UnknownCRSException {
MappedGeometryPropertyType pt = null;
try {
Element contentElement = (Element) XMLTools.getRequiredNode(
element,
"xs:appinfo/deegreewfs:Content",
nsContext );
// sanity check (common error)
Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext );
if ( typeNode != null ) {
throw new XMLParsingException( "Content element must not contain a type attribute." );
}
URI srs = XMLTools.getNodeAsURI( element, "deegreewfs:SRS/text()", nsContext,
this.defaultSRS );
Element mfElement = (Element) XMLTools.getRequiredNode( contentElement,
"deegreewfs:MappingField",
nsContext );
String defaultField = propertyName.getLocalName();
MappingGeometryField mappingField = parseGeometryMappingField( mfElement, defaultField,
table );
List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation",
nsContext );
TableRelation[] tableRelations = new TableRelation[relationElements.size()];
String fromTable = table;
for ( int i = 0; i < tableRelations.length; i++ ) {
tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ),
fromTable );
fromTable = tableRelations[i].getToTable();
}
pt = new MappedGeometryPropertyType( propertyName, typeName, type, minOccurs,
maxOccurs, isIdentityPart, srs, tableRelations,
mappingField );
} catch ( XMLParsingException e ) {
throw new XMLSchemaException( "Error in definition of geometry property '"
+ propertyName + "': " + e.getMessage() );
}
return pt;
}
/**
* Extracts the mapping information for a feature property type from the given "xs:annotation"
* element.
*
* @param element
* "xs:annotation" element
* @param propertyName
* name of the property (local part is used as default field name if it is not
* specified explicitly in the "MappingField" element)
* @param minOccurs
* @param maxOccurs
* @param isIdentityPart
* @param table
* name of the table that is associated with the feature type that this property
* belongs to
* @return feature property type with persistence information
* @throws XMLSchemaException
* if a syntactic or semantic error is found
*/
MappedFeaturePropertyType parseMappedFeaturePropertyType( Element element,
QualifiedName propertyName,
int minOccurs, int maxOccurs,
boolean isIdentityPart, String table )
throws XMLSchemaException {
MappedFeaturePropertyType pt = null;
try {
Element contentElement = (Element) XMLTools.getRequiredNode(
element,
"xs:appinfo/deegreewfs:Content",
nsContext );
// sanity check (common error)
Node mfNode = XMLTools.getNode( element, "deegreewfs:MappingField", nsContext );
if ( mfNode != null ) {
throw new XMLParsingException(
"Content element must not contain a MappingField element." );
}
QualifiedName containedFT = parseQualifiedName( XMLTools.getRequiredNode(
contentElement,
"@type",
nsContext ) );
MappedFeatureTypeReference containedFTRef = new MappedFeatureTypeReference( containedFT );
List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation",
nsContext );
TableRelation[] tableRelations = new TableRelation[relationElements.size()];
String fromTable = table;
for ( int i = 0; i < tableRelations.length; i++ ) {
tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ),
fromTable );
fromTable = tableRelations[i].getToTable();
}
pt = new MappedFeaturePropertyType( propertyName, Types.FEATURE_PROPERTY_NAME,
Types.FEATURE, minOccurs, maxOccurs,
isIdentityPart, tableRelations, containedFTRef );
} catch ( XMLParsingException e ) {
throw new XMLSchemaException( "Error in definition of feature property '"
+ propertyName + "': " + e.getMessage() );
}
return pt;
}
/**
* Parses the given 'MappingField' element.
*
* @param element
* 'MappingField' element
* @param defaultField
* if null, the element must have a 'field' attribute, otherwise the given value is
* used, if the element misses a 'field' attribute
* @param defaultTable
* if null, the element must have a 'table' attribute, otherwise the 'table'
* attribute must be left out or match the given value
* @return class representation of 'MappingField' element
* @throws XMLParsingException
* if a syntactic or semantic error is found
*/
private MappingField parseMappingField( Element element, String defaultField,
String defaultTable )
throws XMLParsingException {
MappingField mappingField = null;
String field = null;
if ( defaultField == null ) {
field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext );
} else {
field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField );
}
String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext );
int typeCode = -1;
try {
typeCode = Types.getTypeCodeForSQLType( typeName );
} catch ( UnknownTypeException e ) {
throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e );
}
String table = null;
if ( defaultTable == null ) {
// if table is unspecified, this is resolved later (in MappedGMLSchema)
// TODO clean this up
table = XMLTools.getNodeAsString( element, "@table", nsContext, null );
} else {
table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable );
if ( !table.equals( defaultTable ) ) {
throw new XMLParsingException(
"Specified 'table' attribute ('"
+ table
+ "') in 'MappingField' element is inconsistent; leave out or use '"
+ defaultTable
+ "' instead." );
}
}
boolean auto = XMLTools.getNodeAsBoolean( element, "@auto", nsContext, false );
mappingField = new MappingField( table, field, typeCode, auto );
return mappingField;
}
/**
* Parses the given 'MappingField' element.
*
* @param element
* 'MappingField' element
* @param defaultField
* if null, the element must have a 'field' attribute, otherwise the given value is
* used, if the element misses a 'field' attribute
* @param defaultTable
* if null, the element must have a 'table' attribute, otherwise the 'table'
* attribute must be left out or match the given value
* @return class representation of 'MappingField' element
* @throws XMLParsingException
* if a syntactic or semantic error is found
*/
private MappingGeometryField parseGeometryMappingField( Element element, String defaultField,
String defaultTable )
throws XMLParsingException {
MappingGeometryField mappingField = null;
String field = null;
if ( defaultField == null ) {
field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext );
} else {
field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField );
}
String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext );
int typeCode = Types.OTHER;
if ( !( "GEOMETRY".equals( typeName ) ) ) {
try {
typeCode = Types.getTypeCodeForSQLType( typeName );
} catch ( UnknownTypeException e ) {
throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e );
}
}
String table = null;
if ( defaultTable == null ) {
// if table is unspecified, this is resolved later (in MappedGMLSchema)
// TODO clean this up
table = XMLTools.getNodeAsString( element, "@table", nsContext, null );
} else {
table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable );
if ( !table.equals( defaultTable ) ) {
String msg = "Specified 'table' attribute ('" + table
+ "') in 'MappingField' element is inconsistent; leave out or use '"
+ defaultTable + "' instead.";
throw new XMLParsingException( msg );
}
}
int internalSrs = XMLTools.getNodeAsInt( element, "@srs", nsContext, -1 );
mappingField = new MappingGeometryField( table, field, typeCode, internalSrs );
return mappingField;
}
/**
* Parses the given 'gmlId' element.
*
* @param element
* 'gmlId' element
* @param table
* the associated table of the FeatureType
* @return class representation of 'gmlId' element
* @throws XMLParsingException
* if a syntactic or semantic error is found
*/
private MappedGMLId parseGMLIdDefinition( Element element, String table )
throws XMLParsingException {
String prefix = XMLTools.getNodeAsString( element, "@prefix", nsContext, "" );
String separator = XMLTools.getNodeAsString( element, "@separator", nsContext, "" );
List mappingFieldElementList = XMLTools.getRequiredNodes( element,
"deegreewfs:MappingField",
nsContext );
MappingField[] mappingFields = new MappingField[mappingFieldElementList.size()];
for ( int i = 0; i < mappingFields.length; i++ ) {
Element mappingFieldElement = (Element) mappingFieldElementList.get( i );
mappingFields[i] = parseMappingField(
mappingFieldElement,
XMLTools.getRequiredNodeAsString(
mappingFieldElement,
"@field",
nsContext ),
table );
}
IDPART_INFO idpart_info = IDPART_INFO.noIDInfo;
String identityPart = XMLTools.getNodeAsString( element, "deegreewfs:IdentityPart/text()",
nsContext, null );
if ( identityPart != null ) {
if ( "false".equals( identityPart ) ) {
idpart_info = IDPART_INFO.notIDPart;
} else {
idpart_info = IDPART_INFO.isIDPart;
}
}
IdGenerator idGenerator = null;
Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:IdGenerator",
nsContext );
if ( idGeneratorElement != null ) {
idGenerator = parseGMLIdGenerator( idGeneratorElement );
} else {
idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() );
}
return new MappedGMLId( prefix, separator, mappingFields, idGenerator, idpart_info );
}
/**
* Parses the given 'IdGenerator' element.
*
* @param element
* 'IdGenerator' element
* @return object representation of 'IdGenerator' element
* @throws XMLParsingException
* if a syntactic or semantic error is found
*/
private IdGenerator parseGMLIdGenerator( Element element )
throws XMLParsingException {
String type = XMLTools.getRequiredNodeAsString( element, "@type", nsContext );
Properties params = new Properties();
List paramElementList = XMLTools.getNodes( element, "deegreewfs:param", nsContext );
Iterator iter = paramElementList.iterator();
while ( iter.hasNext() ) {
Element paramElement = (Element) iter.next();
String name = XMLTools.getRequiredNodeAsString( paramElement, "@name", nsContext );
String value = XMLTools.getRequiredNodeAsString( paramElement, "text()", nsContext );
params.setProperty( name, value );
}
IdGenerator idGenerator = IdGenerator.getInstance( type, params );
return idGenerator;
}
private TableRelation parseTableRelation( Element element, String fromTable )
throws XMLParsingException {
List fromMappingElements = XMLTools.getRequiredNodes(
element,
"deegreewfs:From/deegreewfs:MappingField",
nsContext );
List toMappingElements = XMLTools.getRequiredNodes(
element,
"deegreewfs:To/deegreewfs:MappingField",
nsContext );
if ( fromMappingElements.size() != toMappingElements.size() ) {
throw new XMLParsingException(
"Error in 'Relation' element: number of 'MappingField' elements "
+ "below 'From' and 'To' elements do not match." );
}
FK_INFO fkInfo = FK_INFO.noFKInfo;
boolean fromIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:From/@fk", nsContext,
false );
boolean toIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:To/@fk", nsContext, false );
if ( fromIsFK && toIsFK ) {
throw new XMLParsingException(
"Error in 'Relation' element: either 'To' or 'From' can "
+ "have a 'fk' attribute with value 'true', but not both." );
}
if ( fromIsFK ) {
fkInfo = FK_INFO.fkIsFromField;
}
if ( toIsFK ) {
fkInfo = FK_INFO.fkIsToField;
}
MappingField[] fromMappingFields = new MappingField[fromMappingElements.size()];
MappingField[] toMappingFields = new MappingField[fromMappingFields.length];
for ( int i = 0; i < fromMappingFields.length; i++ ) {
fromMappingFields[i] = parseMappingField( (Element) fromMappingElements.get( i ), null,
fromTable );
toMappingFields[i] = parseMappingField( (Element) toMappingElements.get( i ), null,
null );
}
// parse id generator
// TODO sanity checks
IdGenerator idGenerator = null;
if ( fromIsFK ) {
Element idGeneratorElement = (Element) XMLTools.getNode(
element,
"deegreewfs:To/deegreewfs:IdGenerator",
nsContext );
if ( idGeneratorElement != null ) {
idGenerator = parseGMLIdGenerator( idGeneratorElement );
} else {
idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() );
}
} else {
Element idGeneratorElement = (Element) XMLTools.getNode(
element,
"deegreewfs:From/deegreewfs:IdGenerator",
nsContext );
if ( idGeneratorElement != null ) {
idGenerator = parseGMLIdGenerator( idGeneratorElement );
} else {
idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() );
}
}
return new TableRelation( fromMappingFields, toMappingFields, fkInfo, idGenerator );
}
/**
* Returns the value of the "deegreewfs:visible" element.
*
* @param annotationElement
* @return -1 if it is not present, 0 if it is "false", 1 if it is "true"
* @throws XMLParsingException
*/
public int parseVisible( Element annotationElement )
throws XMLParsingException {
int visibleCode = -1;
String visible = XMLTools.getNodeAsString( annotationElement,
"xs:appinfo/deegreewfs:visible/text()",
nsContext, null );
if ( visible != null ) {
if ( "false".equals( visible ) ) {
visibleCode = 0;
} else {
visibleCode = 1;
}
}
return visibleCode;
}
/**
* Parses the 'updatable' status of the given feature type annotation element.
*
* @param annotationElement
* @return true, if update transactions may be performed on the feature type, false otherwise
* @throws XMLParsingException
*/
public boolean parseIsUpdatable( Element annotationElement )
throws XMLParsingException {
return XMLTools.getNodeAsBoolean( annotationElement,
"xs:appinfo/deegreewfs:transaction/@update", nsContext,
false );
}
/**
* Parses the 'deletable' status of the given feature type annotation element.
*
* @param annotationElement
* @return true, if delete transactions may be performed on the feature type, false otherwise
* @throws XMLParsingException
*/
public boolean parseIsDeletable( Element annotationElement )
throws XMLParsingException {
return XMLTools.getNodeAsBoolean( annotationElement,
"xs:appinfo/deegreewfs:transaction/@delete", nsContext,
false );
}
/**
* Parses the 'insertable' status of the given feature type annotation element.
*
* @param annotationElement
* @return true, if insert transactions may be performed on the feature type, false otherwise
* @throws XMLParsingException
*/
public boolean parseIsInsertable( Element annotationElement )
throws XMLParsingException {
return XMLTools.getNodeAsBoolean( annotationElement,
"xs:appinfo/deegreewfs:transaction/@insert", nsContext,
false );
}
/**
* Returns the value of the "deegreewfs:IdentityPart" element.
*
* @param annotationElement
* @return -1 if it is not present, 0 if it is "false", 1 if it is "true"
* @throws XMLParsingException
*/
public int parseIdentityPart( Element annotationElement )
throws XMLParsingException {
int identityCode = -1;
String identityPart = XMLTools.getNodeAsString(
annotationElement,
"xs:appinfo/deegreewfs:IdentityPart/text()",
nsContext, null );
if ( identityPart != null ) {
if ( "false".equals( identityPart ) ) {
identityCode = 0;
} else {
identityCode = 1;
}
}
return identityCode;
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: MappedGMLSchemaDocument.java,v $
Revision 1.44 2006/11/29 17:00:40 mschneider
Changed internal SRS to integer.
Revision 1.43 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.42 2006/09/26 16:44:27 mschneider
Fixed #extractGlobalAnnotations().
Revision 1.41 2006/09/26 13:01:56 mschneider
Added validation (and setting) of internal SRS in #parseSQLFunctionCall ( Element ).
Revision 1.40 2006/09/20 11:35:41 mschneider
Merged datastore related messages with org.deegree.18n.
Revision 1.39 2006/09/13 18:21:48 mschneider
Added parsing for other content in FunctionParam-elements.
Revision 1.38 2006/09/11 15:04:29 mschneider
FunctionParam elements may no contain GeometryMappingFields.
Revision 1.37 2006/09/07 17:03:41 mschneider
Fixed bug in #extractMaxVariableNumber(String).
Revision 1.36 2006/09/05 17:41:55 mschneider
Added parsing of SQLFunctionCall elements.
Revision 1.35 2006/08/29 15:52:51 mschneider
Fixed handling of empty element and type names.
Revision 1.34 2006/08/28 16:42:35 mschneider
Fixed @Override annotations.
Revision 1.33 2006/08/23 16:33:08 mschneider
Added handling/parsing of ConstantContent.
Revision 1.32 2006/08/23 12:18:02 mschneider
Javadoc fixes.
Revision 1.31 2006/08/22 18:14:42 mschneider
Refactored due to cleanup of org.deegree.io.datastore.schema package.
Revision 1.30 2006/08/21 16:42:36 mschneider
Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package.
Revision 1.29 2006/08/21 15:43:20 mschneider
Cleanup. Added "content" subpackage. Removed (completely unused and outdated) FeatureArrayPropertyType.
Revision 1.28 2006/08/06 20:27:47 poth
unneccessary type cast removed
Revision 1.27 2006/04/19 18:24:03 mschneider
Added identity part for feature ids.
Revision 1.26 2006/04/18 12:45:06 mschneider
Added sanity checks to prevent common misconfigurations.
Revision 1.25 2006/04/06 20:25:23 poth
*** empty log message ***
Revision 1.24 2006/04/04 20:39:41 poth
*** empty log message ***
Revision 1.23 2006/04/04 10:28:31 mschneider
Added identityPart handling. Used for equality definition of feature types.
Revision 1.22 2006/03/30 21:20:24 poth
*** empty log message ***
Revision 1.21 2006/03/02 18:03:51 poth
*** empty log message ***
Revision 1.20 2006/02/17 15:00:23 mschneider
Made fk information in Relation element optional (works only for read only datastores).
Revision 1.19 2006/02/17 14:34:58 mschneider
Changed due to refactoring of GMLIdGenerator.
Revision 1.18 2006/02/09 10:36:35 mschneider
Fixed handling of "deegreewfs:visible" annotation.
Revision 1.17 2006/02/08 17:42:10 mschneider
Added handling of suppressXLinkOutput parameter.
********************************************************************** */