//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sql/wherebuilder/QueryTableTree.java,v 1.22 2006/09/20 11:35:41 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.sql.wherebuilder; import java.util.ArrayList; import java.util.List; 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.i18n.Messages; import org.deegree.io.datastore.PropertyPathResolvingException; import org.deegree.io.datastore.schema.MappedFeaturePropertyType; import org.deegree.io.datastore.schema.MappedFeatureType; import org.deegree.io.datastore.schema.MappedGMLId; import org.deegree.io.datastore.schema.MappedGMLSchema; import org.deegree.io.datastore.schema.MappedGeometryPropertyType; import org.deegree.io.datastore.schema.MappedPropertyType; import org.deegree.io.datastore.schema.MappedSimplePropertyType; import org.deegree.io.datastore.schema.TableRelation; import org.deegree.io.datastore.schema.content.ConstantContent; import org.deegree.io.datastore.schema.content.SimpleContent; import org.deegree.io.datastore.sql.TableAliasGenerator; import org.deegree.model.feature.schema.FeatureType; import org.deegree.ogcbase.AttributeStep; import org.deegree.ogcbase.CommonNamespaces; import org.deegree.ogcbase.PropertyPath; import org.deegree.ogcbase.PropertyPathStep; /** * Represents {@link PropertyPath} instances (properties used in an OGC filter and as sort * criteria) and their mapping to a certain relational schema. * <p> * Encapsulates the associated {@link MappedFeatureType} and the corresponding table name. Also * contains joined tables; joined tables represent selected complex (feature) properties. Joined * tables are {@link QueryTableTree} instances themselves, so the whole structure is recursive and * forms a tree. * <p> * Every join to a table (=subfeature type) is discriminated using the * {@link MappedFeaturePropertyType}. This is necessary, because a feature type may contain * several instances of one subfeature type (in different properties). This also implies that it * is sometimes necessary to join a table to another table more than once, but with different * (unique) aliases. * * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> * @author last edited by: $Author: mschneider $ * * @version $Revision: 1.22 $, $Date: 2006/09/20 11:35:41 $ */ public class QueryTableTree { private static final ILogger LOG = LoggerFactory.getLogger( QueryTableTree.class ); private TableAliasGenerator aliasGenerator; private FeatureTypeNode root; private MappedGMLSchema schema; // uses 2 lists instead of Map, because PropertyPath.equals() is overwritten, // and identity (==) is needed here (different occurences of "equal" PropertyName // in filter must be treated as different PropertyPaths) private List<PropertyPath> propertyPaths = new ArrayList<PropertyPath>(); private List<PropertyNode> propertyNodes = new ArrayList<PropertyNode>(); /** * Creates a new <code>QueryTableTree</code>. * * @param featureType * @param aliasGenerator */ public QueryTableTree( MappedFeatureType featureType, TableAliasGenerator aliasGenerator ) { if ( aliasGenerator != null ) { this.aliasGenerator = aliasGenerator; } else { this.aliasGenerator = new TableAliasGenerator(); } this.schema = featureType.getGMLSchema(); this.root = new FeatureTypeNode( featureType, aliasGenerator.generateUniqueAlias() ); } /** * Returns the root feature type node of the tree. * * @return the root feature type node of the tree */ public FeatureTypeNode getRootNode() { return this.root; } /** * Returns the alias for the root table. * * @return the alias for the root table */ public String getRootAlias() { return this.root.getAlias(); } /** * Returns the property node for the given property path. * * @param path * property to be looked up * @return the property node for the given property path */ public PropertyNode getPropertyNode( PropertyPath path ) { PropertyNode node = null; for ( int i = 0; i < this.propertyPaths.size(); i++ ) { if ( this.propertyPaths.get( i ) == path ) { node = this.propertyNodes.get( i ); break; } } return node; } /** * Tries to insert the given {@link PropertyPath} as a filter criterion into the query tree. * <p> * The {@link PropertyPath} is validated during insertion. * * @param property * property to be inserted, has to have at least one step * @throws PropertyPathResolvingException * if the path violates the feature type's schema */ public void addFilterProperty( PropertyPath property ) throws PropertyPathResolvingException { MappedPropertyType pt = validate( property, false ); if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); if ( content instanceof ConstantContent ) { // add SimplePropertyNode to root node (because table path is irrelevant) String[] tableAliases = generateTableAliases( pt ); PropertyNode propertyNode = new SimplePropertyNode( (MappedSimplePropertyType) pt, root, tableAliases ); this.propertyPaths.add( property ); this.propertyNodes.add( propertyNode ); //root.addPropertyNode( propertyNode ); } else { insert( property ); } } else { insert( property ); } } /** * Tries to insert the given {@link PropertyPath} as a sort criterion into the tree. * <p> * The {@link PropertyPath} is validated during insertion. It is also checked that the * path is unique, i.e. every property type on the path must have maxOccurs set to 1. * * @param property * property to be inserted, has to have at least one step * @throws PropertyPathResolvingException * if the path violates the feature type's schema */ public void addSortProperty( PropertyPath property ) throws PropertyPathResolvingException { MappedPropertyType pt = validate( property, false ); if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); if ( content.isSortable() ) { insert( property ); } else { String msg = "Skipping property '" + property + "' as sort criterion."; LOG.logDebug( msg ); // add SimplePropertyNode to root node (because table path is irrelevant) String[] tableAliases = generateTableAliases( pt ); PropertyNode propertyNode = new SimplePropertyNode( (MappedSimplePropertyType) pt, root, tableAliases ); this.propertyPaths.add( property ); this.propertyNodes.add( propertyNode ); //root.addPropertyNode( propertyNode ); } } else { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_SORT1", property ); throw new PropertyPathResolvingException( msg ); } } /** * Validates the {@link PropertyPath} against the {@link MappedGMLSchema} and returns the * {@link MappedPropertyType} that the paths ends with. * * @param propertyPath * PropertyPath to be validated, has to have at least one step * @param forceUniquePath * if set to true, an exeption is thrown if the path is not unique, i.e. at least * one property on the path has maxOccurs set to a value > 1 * @return the type of the property that the path ends with * @throws PropertyPathResolvingException * if the path violates the feature type's schema */ public MappedPropertyType validate( PropertyPath propertyPath, boolean forceUniquePath ) throws PropertyPathResolvingException { MappedPropertyType propertyType = null; MappedFeatureType currentFT = this.root.getFeatureType(); LOG.logDebug( "Trying to validate '" + propertyPath + "' against schema of feature type '" + currentFT + "'..." ); int firstPropertyPos = 0; QualifiedName elementName = propertyPath.getStep( firstPropertyPos ).getPropertyName(); // must be the name of the feature type or the name of a property of the feature type if ( elementName.equals( currentFT.getName() ) ) { LOG.logDebug( "First step matches the name of the feature type." ); firstPropertyPos++; } else { LOG.logDebug( "First step does not match the name of the feature type. " + "Must be the name of a property then." ); } for ( int step = firstPropertyPos; step < propertyPath.getSteps(); step += 2 ) { LOG.logDebug( "Looking up property: " + propertyPath.getStep( step ).getPropertyName() ); propertyType = getPropertyType( currentFT, propertyPath.getStep( step ) ); if ( propertyType == null ) { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE4", propertyPath, step, propertyPath.getStep( step ), currentFT.getName(), propertyPath.getStep( step ) ); throw new PropertyPathResolvingException( msg ); } if ( forceUniquePath ) { if ( propertyType.getMaxOccurs() != 1 ) { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_SORT2", propertyPath, step, propertyType.getName() ); throw new PropertyPathResolvingException( msg ); } } if ( propertyType instanceof MappedSimplePropertyType ) { if ( step < propertyPath.getSteps() - 1 ) { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE1", propertyPath, step, propertyType.getName(), "simple" ); throw new PropertyPathResolvingException( msg ); } } else if ( propertyType instanceof MappedGeometryPropertyType ) { if ( step < propertyPath.getSteps() - 1 ) { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE1", propertyPath, step, propertyType.getName(), "geometry" ); throw new PropertyPathResolvingException( msg ); } } else if ( propertyType instanceof MappedFeaturePropertyType ) { if ( step == propertyPath.getSteps() - 1 ) { String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE2", propertyPath, step, propertyType.getName() ); throw new PropertyPathResolvingException( msg ); } MappedFeaturePropertyType pt = (MappedFeaturePropertyType) propertyType; FeatureType[] allowedTypes = this.schema.getSubstitutions( pt.getFeatureTypeReference().getFeatureType() ); QualifiedName givenTypeName = propertyPath.getStep( step + 1 ).getPropertyName(); MappedFeatureType givenType = null; for ( int i = 0; i < allowedTypes.length; i++ ) { if ( allowedTypes[i].getName().equals( givenTypeName ) ) { givenType = (MappedFeatureType) allowedTypes[i]; break; } } if ( givenType == null ) { StringBuffer validTypeList = new StringBuffer(); for ( int i = 0; i < allowedTypes.length; i++ ) { validTypeList.append( '\'' ); validTypeList.append( allowedTypes[i].getName() ); validTypeList.append( '\'' ); if ( i != allowedTypes.length - 1 ) { validTypeList.append( ", " ); } } String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE3", propertyPath, step + 1, givenTypeName, validTypeList ); throw new PropertyPathResolvingException( msg.toString() ); } currentFT = pt.getFeatureTypeReference().getFeatureType(); } else { assert ( false ); } } return propertyType; } /** * Tries to insert the given {@link PropertyPath} into the query tree. * <p> * The {@link PropertyPath} is validated during insertion. * * @param propertyPath * PropertyPath to be inserted into the tree, has to have at least one step */ private void insert( PropertyPath propertyPath ) { LOG.logDebug( "Trying to insert '" + propertyPath + "' into the query table tree." ); int firstPropertyPos = 0; PropertyPathStep step = propertyPath.getStep( firstPropertyPos ); QualifiedName elementName = step.getPropertyName(); // must be the name of the feature type or the name of a property of the feature type if ( elementName.equals( root.getFeatureType().getName() ) ) { LOG.logDebug( "First step matches the name of the feature type." ); firstPropertyPos++; } else { LOG.logDebug( "First step does not match the name of the feature type. " + "Must be the name of a property then." ); } FeatureTypeNode featureTypeNode = root; PropertyNode propertyNode = null; for ( int i = firstPropertyPos; i < propertyPath.getSteps(); i += 2 ) { // check for property with step name in the feature type MappedFeatureType featureType = featureTypeNode.getFeatureType(); elementName = propertyPath.getStep( i ).getPropertyName(); MappedPropertyType propertyType = getPropertyType( featureType, propertyPath.getStep( i ) ); // check for property node in the feature type node propertyNode = featureTypeNode.getPropertyNode( propertyType ); if ( propertyNode == null || propertyNode.getProperty().getMaxOccurs() != 1 ) { addPathFragment( featureTypeNode, propertyPath, i ); break; } if ( i + 1 < propertyPath.getSteps() ) { // more steps? propertyNode must be a FeaturePropertyNode then elementName = propertyPath.getStep( i + 1 ).getPropertyName(); if ( propertyNode instanceof FeaturePropertyNode ) { FeatureTypeNode[] childFeatureTypeNodes = ( (FeaturePropertyNode) propertyNode ).getFeatureTypeNodes(); boolean found = false; for ( int j = 0; j < childFeatureTypeNodes.length; j++ ) { if ( elementName.equals( childFeatureTypeNodes[j].getFeatureType().getName() ) ) { found = true; featureTypeNode = childFeatureTypeNodes[j]; break; } } if ( !found ) { // add another feature type node FeaturePropertyNode featurePropertyNode = (FeaturePropertyNode) propertyNode; MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) propertyType; FeatureTypeNode newFeatureTypeNode = getFeatureTypeNode( featurePT, propertyPath, i + 1 ); addPathFragment( newFeatureTypeNode, propertyPath, i + 2 ); featurePropertyNode.addFeatureTypeNode( newFeatureTypeNode ); return; } } else { assert ( false ); } } else { assert ( false ); } } // "equal" path is already registered, map this one to existing instance if ( getPropertyNode( propertyPath ) == null ) { this.propertyPaths.add( propertyPath ); this.propertyNodes.add( propertyNode ); } } private void addPathFragment( FeatureTypeNode featureTypeNode, PropertyPath propertyPath, int startStep ) { for ( int step = startStep; step < propertyPath.getSteps(); step += 2 ) { LOG.logDebug( "Looking up property: " + propertyPath.getStep( step ).getPropertyName() ); MappedPropertyType propertyType = getPropertyType( featureTypeNode.getFeatureType(), propertyPath.getStep( step ) ); if ( propertyType instanceof MappedSimplePropertyType ) { addSimplePropertyNode( featureTypeNode, (MappedSimplePropertyType) propertyType, propertyPath, step ); break; } else if ( propertyType instanceof MappedGeometryPropertyType ) { addGeometryPropertyNode( featureTypeNode, (MappedGeometryPropertyType) propertyType, propertyPath, step ); break; } else if ( propertyType instanceof MappedFeaturePropertyType ) { MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) propertyType; featureTypeNode = addFeaturePropertyNode( featureTypeNode, featurePT, propertyPath, step ); } else { assert ( false ); } } } /** * Returns the {@link MappedPropertyType} for the given {@link MappedFeatureType} that matches * the given {@link PropertyPathStep}. * * @param featureType * @param step * @return matching property type or null, if none exists */ private MappedPropertyType getPropertyType( MappedFeatureType featureType, PropertyPathStep step ) { MappedPropertyType propertyType = null; QualifiedName name = step.getPropertyName(); if ( step instanceof AttributeStep ) { // TODO remove handling of gml:id here (after adaptation of feature model) if ( CommonNamespaces.GMLNS.equals( name.getNamespace() ) && "id".equals( name.getLocalName() ) ) { MappedGMLId gmlId = featureType.getGMLId(); propertyType = new MappedSimplePropertyType( name, Types.VARCHAR, 1, 1, false, null, gmlId.getIdFields()[0] ); } } else { // "normal" property (not gml:id) propertyType = (MappedPropertyType) featureType.getProperty( name ); } return propertyType; } private void addSimplePropertyNode( FeatureTypeNode featureTypeNode, MappedSimplePropertyType propertyType, PropertyPath propertyPath, int step ) { assert ( step == propertyPath.getSteps() - 1 ); String[] tableAliases = generateTableAliases( propertyType ); PropertyNode propertyNode = new SimplePropertyNode( propertyType, featureTypeNode, tableAliases ); this.propertyPaths.add( propertyPath ); this.propertyNodes.add( propertyNode ); featureTypeNode.addPropertyNode( propertyNode ); } private void addGeometryPropertyNode( FeatureTypeNode featureTypeNode, MappedGeometryPropertyType propertyType, PropertyPath propertyPath, int step ) { assert ( step == propertyPath.getSteps() - 1 ); String[] tableAliases = generateTableAliases( propertyType ); PropertyNode propertyNode = new GeometryPropertyNode( propertyType, featureTypeNode, tableAliases ); this.propertyPaths.add( propertyPath ); this.propertyNodes.add( propertyNode ); featureTypeNode.addPropertyNode( propertyNode ); } private FeatureTypeNode addFeaturePropertyNode( FeatureTypeNode featureTypeNode, MappedFeaturePropertyType pt, PropertyPath propertyPath, int step ) { assert ( step < propertyPath.getSteps() - 1 ); FeatureType[] allowedTypes = this.schema.getSubstitutions( pt.getFeatureTypeReference().getFeatureType() ); QualifiedName givenTypeName = propertyPath.getStep( step + 1 ).getPropertyName(); MappedFeatureType givenType = null; for ( int i = 0; i < allowedTypes.length; i++ ) { if ( allowedTypes[i].getName().equals( givenTypeName ) ) { givenType = (MappedFeatureType) allowedTypes[i]; break; } } assert ( givenType != null ); // TODO make proper String[] tableAliases = this.aliasGenerator.generateUniqueAliases( pt.getTableRelations().length - 1 ); String tableAlias = this.aliasGenerator.generateUniqueAlias(); FeatureTypeNode childFeatureTypeNode = new FeatureTypeNode( givenType, tableAlias ); FeatureType featureType = pt.getFeatureTypeReference().getFeatureType(); LOG.logDebug( "featureType: " + featureType.getName() ); PropertyNode propertyNode = new FeaturePropertyNode( pt, featureTypeNode, tableAliases, childFeatureTypeNode ); // this.propertyPaths.add (propertyPath); // this.propertyNodes.add (propertyNode); featureTypeNode.addPropertyNode( propertyNode ); return childFeatureTypeNode; } private FeatureTypeNode getFeatureTypeNode( MappedFeaturePropertyType content, PropertyPath propertyPath, int step ) { FeatureType[] allowedTypes = this.schema.getSubstitutions( content.getFeatureTypeReference().getFeatureType() ); QualifiedName givenTypeName = propertyPath.getStep( step ).getPropertyName(); MappedFeatureType givenType = null; for ( int i = 0; i < allowedTypes.length; i++ ) { if ( allowedTypes[i].getName().equals( givenTypeName ) ) { givenType = (MappedFeatureType) allowedTypes[i]; break; } } assert ( givenType != null ); String tableAlias = this.aliasGenerator.generateUniqueAlias(); FeatureTypeNode childFeatureTypeNode = new FeatureTypeNode( givenType, tableAlias ); return childFeatureTypeNode; } private String[] generateTableAliases( MappedPropertyType pt ) { String[] aliases = null; TableRelation[] relations = pt.getTableRelations(); if ( relations != null ) { aliases = new String[relations.length]; for ( int i = 0; i < aliases.length; i++ ) { aliases[i] = this.aliasGenerator.generateUniqueAlias(); } } return aliases; } @Override public String toString() { return root.toString( "" ); } } /* ************************************************************************************************** * Changes to this class. What the people have been up to: * * $Log: QueryTableTree.java,v $ * Revision 1.22 2006/09/20 11:35:41 mschneider * Merged datastore related messages with org.deegree.18n. * * Revision 1.21 2006/09/19 16:16:25 poth * *** empty log message *** * * Revision 1.20 2006/09/19 14:56:04 mschneider * Fixed error message. * * Revision 1.19 2006/09/05 14:44:31 mschneider * Splitted validation and adding of PropertyPaths. Added handling of sort properties and constant content. * * Revision 1.18 2006/09/04 14:16:14 mschneider * Cleaned up handling if no TableAliasGenerator is given. * * Revision 1.17 2006/09/04 13:57:23 mschneider * Removed unused member variable allFeatureTypeNodes. * * Revision 1.16 2006/08/28 16:38:59 mschneider * Javadoc fixes. * * Revision 1.15 2006/08/24 06:40:05 poth * File header corrected * * Revision 1.14 2006/08/22 18:14:42 mschneider * Refactored due to cleanup of org.deegree.io.datastore.schema package. * * Revision 1.13 2006/08/21 15:47:18 mschneider * Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package. * * Revision 1.12 2006/08/15 17:41:28 mschneider * Changed signature of #add ( PropertyPath[] ) to #add( List<PropertyPath> ). * * Revision 1.11 2006/08/14 16:50:17 mschneider * Improved javadoc. * * Revision 1.10 2006/05/21 19:09:02 poth * several methods set to public; required by SDE datastore * ************************************************************************************************* */