001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/wherebuilder/QueryTableTree.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.io.datastore.sql.wherebuilder;
044    
045    import java.util.ArrayList;
046    import java.util.HashMap;
047    import java.util.List;
048    import java.util.Map;
049    
050    import org.deegree.datatypes.QualifiedName;
051    import org.deegree.datatypes.Types;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.i18n.Messages;
055    import org.deegree.io.datastore.PropertyPathResolvingException;
056    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
057    import org.deegree.io.datastore.schema.MappedFeatureType;
058    import org.deegree.io.datastore.schema.MappedGMLId;
059    import org.deegree.io.datastore.schema.MappedGMLSchema;
060    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
061    import org.deegree.io.datastore.schema.MappedPropertyType;
062    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
063    import org.deegree.io.datastore.schema.TableRelation;
064    import org.deegree.io.datastore.schema.content.ConstantContent;
065    import org.deegree.io.datastore.schema.content.SimpleContent;
066    import org.deegree.io.datastore.sql.TableAliasGenerator;
067    import org.deegree.ogcbase.AttributeStep;
068    import org.deegree.ogcbase.CommonNamespaces;
069    import org.deegree.ogcbase.PropertyPath;
070    import org.deegree.ogcbase.PropertyPathStep;
071    
072    /**
073     * Represents selected {@link MappedFeatureType}s and {@link PropertyPath} instances (properties used in an OGC filter
074     * and as sort criteria) and their mapping to a certain relational schema.
075     * <p>
076     * The requested {@link MappedFeatureType}s are the root nodes of the tree. If there is more than root node (feature
077     * type), the query requests a join between feature types.
078     * 
079     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
080     * @author last edited by: $Author: apoth $
081     * 
082     * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
083     */
084    public class QueryTableTree {
085    
086        private static final ILogger LOG = LoggerFactory.getLogger( QueryTableTree.class );
087    
088        private TableAliasGenerator aliasGenerator;
089    
090        private FeatureTypeNode[] roots;
091    
092        // maps the aliases (specified in the query) to the corresponding FeatureTypeNode
093        private Map<String, FeatureTypeNode> ftAliasToRootFtNode = new HashMap<String, FeatureTypeNode>();
094    
095        // uses 2 lists instead of Map, because PropertyPath.equals() is overwritten,
096        // and identity (==) is needed here (different occurences of "equal" PropertyName
097        // in filter must be treated as different PropertyPaths)
098        private List<PropertyPath> propertyPaths = new ArrayList<PropertyPath>();
099    
100        private List<PropertyNode> propertyNodes = new ArrayList<PropertyNode>();
101    
102        /**
103         * Creates a new instance of <code>QueryTableTree</code>.
104         * 
105         * @param rootFts
106         *            selected feature types, more than one type means that the types are joined
107         * @param aliases
108         *            aliases for the feature types, may be null (must have same length as rootFts otherwise)
109         * @param aliasGenerator
110         *            aliasGenerator to be used to generate table aliases, may be null
111         */
112        public QueryTableTree( MappedFeatureType[] rootFts, String[] aliases, TableAliasGenerator aliasGenerator ) {
113    
114            if ( aliasGenerator != null ) {
115                this.aliasGenerator = aliasGenerator;
116            } else {
117                this.aliasGenerator = new TableAliasGenerator();
118            }
119            this.roots = new FeatureTypeNode[rootFts.length];
120            for ( int i = 0; i < rootFts.length; i++ ) {
121                FeatureTypeNode rootFtNode = new FeatureTypeNode( rootFts[i], aliases != null ? aliases[i] : null,
122                                                                  aliasGenerator.generateUniqueAlias() );
123                this.roots[i] = rootFtNode;
124                if ( aliases != null ) {
125                    this.ftAliasToRootFtNode.put( aliases[i], rootFtNode );
126                    LOG.logDebug( "Alias '" + aliases[i] + "' maps to '" + rootFtNode + "'." );
127                }
128            }
129        }
130    
131        /**
132         * Returns the root feature type nodes of the tree.
133         * 
134         * @return the root feature type nodes of the tree, contains at least one entry
135         */
136        public FeatureTypeNode[] getRootNodes() {
137            return this.roots;
138        }
139    
140        /**
141         * Returns the alias for the root table.
142         * 
143         * TODO support more than one root node
144         * 
145         * @return the alias for the root table
146         */
147        public String getRootAlias() {
148            return this.roots[0].getTableAlias();
149        }
150    
151        /**
152         * Returns the property node for the given property path.
153         * 
154         * @param path
155         *            property to be looked up
156         * @return the property node for the given property path
157         */
158        public PropertyNode getPropertyNode( PropertyPath path ) {
159    
160            PropertyNode node = null;
161            for ( int i = 0; i < this.propertyPaths.size(); i++ ) {
162                if ( this.propertyPaths.get( i ) == path ) {
163                    node = this.propertyNodes.get( i );
164                    break;
165                }
166            }
167            return node;
168        }
169    
170        /**
171         * Tries to insert the given {@link PropertyPath} as a filter criterion into the tree.
172         * <p>
173         * The {@link PropertyPath} is validated during insertion.
174         * 
175         * @param property
176         *            property to be inserted, has to have at least one step
177         * @throws PropertyPathResolvingException
178         *             if the path violates the feature type's schema
179         */
180        public void addFilterProperty( PropertyPath property )
181                                throws PropertyPathResolvingException {
182            MappedPropertyType pt = validate( property, false );
183            if ( pt instanceof MappedSimplePropertyType ) {
184                SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent();
185                if ( content instanceof ConstantContent ) {
186                    // add SimplePropertyNode to root node (because table path is irrelevant)
187                    String[] tableAliases = generateTableAliases( pt );
188                    PropertyNode propertyNode = new SimplePropertyNode( (MappedSimplePropertyType) pt, roots[0],
189                                                                        tableAliases );
190                    this.propertyPaths.add( property );
191                    this.propertyNodes.add( propertyNode );
192                    // root.addPropertyNode( propertyNode );
193                } else {
194                    insertValidatedPath( property );
195                }
196            } else {
197                insertValidatedPath( property );
198            }
199        }
200    
201        /**
202         * Tries to insert the given {@link PropertyPath} as a sort criterion into the tree.
203         * <p>
204         * The {@link PropertyPath} is validated during insertion. It is also checked that the path is unique, i.e. every
205         * property type on the path must have maxOccurs set to 1.
206         * 
207         * @param property
208         *            property to be inserted, has to have at least one step
209         * @throws PropertyPathResolvingException
210         *             if the path violates the feature type's schema
211         */
212        public void addSortProperty( PropertyPath property )
213                                throws PropertyPathResolvingException {
214            MappedPropertyType pt = validate( property, false );
215            if ( pt instanceof MappedSimplePropertyType ) {
216                SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent();
217                if ( content.isSortable() ) {
218                    insertValidatedPath( property );
219                } else {
220                    String msg = "Skipping property '" + property + "' as sort criterion.";
221                    LOG.logDebug( msg );
222                    // add SimplePropertyNode to root node (because table path is irrelevant)
223                    String[] tableAliases = generateTableAliases( pt );
224                    PropertyNode propertyNode = new SimplePropertyNode( (MappedSimplePropertyType) pt, roots[0],
225                                                                        tableAliases );
226                    this.propertyPaths.add( property );
227                    this.propertyNodes.add( propertyNode );
228                    // root.addPropertyNode( propertyNode );
229                }
230            } else {
231                String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_SORT1", property );
232                throw new PropertyPathResolvingException( msg );
233            }
234        }
235    
236        /**
237         * Validates the (normalized) {@link PropertyPath} against the {@link MappedGMLSchema} and returns the
238         * {@link MappedPropertyType} that the path refers to.
239         * 
240         * @param propertyPath
241         *            PropertyPath to be validated, has to have at least one step
242         * @param forceUniquePath
243         *            if set to true, an exeption is thrown if the path is not unique, i.e. at least one property on the
244         *            path has maxOccurs set to a value > 1
245         * @return the property type that the path ends with
246         * @throws PropertyPathResolvingException
247         *             if the path violates the feature type's schema
248         */
249        private MappedPropertyType validate( PropertyPath propertyPath, boolean forceUniquePath )
250                                throws PropertyPathResolvingException {
251    
252            MappedPropertyType endingPt = null;
253            MappedFeatureType currentFt = null;
254            int firstPropertyPos = 1;
255    
256            // check if path starts with (valid) alias
257            QualifiedName firstStep = propertyPath.getStep( 0 ).getPropertyName();
258            if ( firstStep.getLocalName().startsWith( "$" ) ) {
259                // path starts with alias
260                String ftAlias = firstStep.getLocalName().substring( 1 );
261                FeatureTypeNode rootNode = this.ftAliasToRootFtNode.get( ftAlias );
262                if ( rootNode == null ) {
263                    String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE6", propertyPath,
264                                                      firstStep.getLocalName() );
265                    throw new PropertyPathResolvingException( msg );
266                }
267                currentFt = rootNode.getFeatureType();
268            } else {
269                // path does not start with an alias
270                if ( this.roots.length > 1 ) {
271                    LOG.logDebug( "Multiple (join) feature type request. First step of '" + propertyPath
272                                  + "' must be the name (or an alias) of a requested feature type then..." );
273                    QualifiedName ftName = propertyPath.getStep( 0 ).getPropertyName();
274                    for ( FeatureTypeNode rootNode : this.roots ) {
275                        if ( rootNode.getFeatureType().getName().equals( ftName ) ) {
276                            currentFt = rootNode.getFeatureType();
277                            break;
278                        }
279                    }
280                    if ( currentFt == null ) {
281                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE5", propertyPath,
282                                                          propertyPath.getStep( 0 ) );
283                        throw new PropertyPathResolvingException( msg );
284                    }
285                } else {
286                    currentFt = this.roots[0].getFeatureType();
287                    LOG.logDebug( "Single feature type request. Trying to validate '" + propertyPath
288                                  + "' against schema of feature type '" + currentFt.getName() + "'..." );
289    
290                    QualifiedName elementName = propertyPath.getStep( 0 ).getPropertyName();
291    
292                    // must be the name of the feature type or the name of a property of the feature
293                    // type
294                    if ( elementName.equals( currentFt.getName() ) ) {
295                        LOG.logDebug( "First step matches the name of the feature type." );
296                    } else {
297                        LOG.logDebug( "First step does not match the name of the feature type. "
298                                      + "Must be the name of a property then." );
299                        firstPropertyPos = 0;
300                    }
301                }
302            }
303    
304            for ( int step = firstPropertyPos; step < propertyPath.getSteps(); step += 2 ) {
305                LOG.logDebug( "Looking up property: " + propertyPath.getStep( step ).getPropertyName() );
306                endingPt = getPropertyType( currentFt, propertyPath.getStep( step ) );
307    
308                if ( endingPt == null ) {
309                    String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE4", propertyPath, step,
310                                                      propertyPath.getStep( step ), currentFt.getName(),
311                                                      propertyPath.getStep( step ) );
312                    throw new PropertyPathResolvingException( msg );
313                }
314    
315                if ( forceUniquePath ) {
316                    if ( endingPt.getMaxOccurs() != 1 ) {
317                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_SORT2", propertyPath, step,
318                                                          endingPt.getName() );
319                        throw new PropertyPathResolvingException( msg );
320                    }
321                }
322    
323                if ( endingPt instanceof MappedSimplePropertyType ) {
324                    if ( step < propertyPath.getSteps() - 1 ) {
325                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE1", propertyPath, step,
326                                                          endingPt.getName(), "simple" );
327                        throw new PropertyPathResolvingException( msg );
328                    }
329                } else if ( endingPt instanceof MappedGeometryPropertyType ) {
330                    if ( step < propertyPath.getSteps() - 1 ) {
331                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE1", propertyPath, step,
332                                                          endingPt.getName(), "geometry" );
333                        throw new PropertyPathResolvingException( msg );
334                    }
335                } else if ( endingPt instanceof MappedFeaturePropertyType ) {
336                    if ( step == propertyPath.getSteps() - 1 ) {
337                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE2", propertyPath, step,
338                                                          endingPt.getName() );
339                        throw new PropertyPathResolvingException( msg );
340                    }
341                    MappedFeaturePropertyType pt = (MappedFeaturePropertyType) endingPt;
342                    MappedFeatureType[] allowedTypes = pt.getFeatureTypeReference().getFeatureType().getConcreteSubstitutions();
343                    QualifiedName givenTypeName = propertyPath.getStep( step + 1 ).getPropertyName();
344    
345                    // check if an alias is used
346                    if ( givenTypeName.getLocalName().startsWith( "$" ) ) {
347                        String ftAlias = givenTypeName.getLocalName().substring( 1 );
348                        FeatureTypeNode ftNode = this.ftAliasToRootFtNode.get( ftAlias );
349                        if ( ftNode == null ) {
350                            String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE6", propertyPath, step + 1,
351                                                              propertyPath.getStep( step + 1 ) );
352                            throw new PropertyPathResolvingException( msg );
353                        }
354                        givenTypeName = ftNode.getFeatureType().getName();
355                    }
356    
357                    MappedFeatureType givenType = null;
358    
359                    for ( int i = 0; i < allowedTypes.length; i++ ) {
360                        if ( allowedTypes[i].getName().equals( givenTypeName ) ) {
361                            givenType = allowedTypes[i];
362                            break;
363                        }
364                    }
365                    if ( givenType == null ) {
366                        StringBuffer validTypeList = new StringBuffer();
367                        for ( int i = 0; i < allowedTypes.length; i++ ) {
368                            validTypeList.append( '\'' );
369                            validTypeList.append( allowedTypes[i].getName() );
370                            validTypeList.append( '\'' );
371                            if ( i != allowedTypes.length - 1 ) {
372                                validTypeList.append( ", " );
373                            }
374                        }
375                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE3", propertyPath, step + 1,
376                                                          propertyPath.getStep( step + 1 ), validTypeList );
377                        throw new PropertyPathResolvingException( msg.toString() );
378                    }
379                    currentFt = pt.getFeatureTypeReference().getFeatureType();
380                } else {
381                    assert ( false );
382                }
383            }
384            return endingPt;
385        }
386    
387        /**
388         * Inserts the (already validated!!!) {@link PropertyPath} into the query tree.
389         * 
390         * @see #validate(PropertyPath, boolean)
391         * 
392         * @param propertyPath
393         *            validated PropertyPath to be inserted into the tree
394         */
395        private void insertValidatedPath( PropertyPath propertyPath ) {
396    
397            LOG.logDebug( "Inserting '" + propertyPath + "' into the query table tree." );
398    
399            FeatureTypeNode ftNode = null;
400            int firstPropertyPos = 1;
401    
402            // check if path starts with an alias
403            QualifiedName firstStep = propertyPath.getStep( 0 ).getPropertyName();
404            if ( firstStep.getLocalName().startsWith( "$" ) ) {
405                String ftAlias = firstStep.getLocalName().substring( 1 );
406                ftNode = this.ftAliasToRootFtNode.get( ftAlias );
407            } else {
408                if ( this.roots.length > 1 ) {
409                    QualifiedName ftName = propertyPath.getStep( 0 ).getPropertyName();
410                    for ( FeatureTypeNode rootNode : this.roots ) {
411                        if ( rootNode.getFeatureType().getName().equals( ftName ) ) {
412                            ftNode = rootNode;
413                            break;
414                        }
415                    }
416                } else {
417                    PropertyPathStep step = propertyPath.getStep( 0 );
418                    QualifiedName elementName = step.getPropertyName();
419    
420                    // must be the name of the feature type or the name of a property of the feature type
421                    if ( elementName.equals( this.roots[0].getFeatureType().getName() ) ) {
422                        LOG.logDebug( "First step matches the name of the feature type." );
423                    } else {
424                        LOG.logDebug( "First step does not match the name of the feature type. "
425                                      + "Must be the name of a property then." );
426                        firstPropertyPos = 0;
427                    }
428                    ftNode = this.roots[0];
429                }
430            }
431    
432            PropertyNode propNode = null;
433            for ( int i = firstPropertyPos; i < propertyPath.getSteps(); i += 2 ) {
434    
435                // check for property with step name in the feature type
436                MappedFeatureType currentFt = ftNode.getFeatureType();
437                MappedPropertyType pt = getPropertyType( currentFt, propertyPath.getStep( i ) );
438    
439                // check if feature type node already has such a property node, add it otherwise
440                propNode = ftNode.getPropertyNode( pt );
441                if ( propNode == null || propNode.getProperty().getMaxOccurs() != 1 ) {
442                    propNode = createPropertyNode( ftNode, pt );
443                    ftNode.addPropertyNode( propNode );
444                }
445    
446                if ( i + 1 < propertyPath.getSteps() ) {
447                    // more steps? propNode must be a FeaturePropertyNode then
448                    assert propNode instanceof FeaturePropertyNode;
449                    QualifiedName featureStep = propertyPath.getStep( i + 1 ).getPropertyName();
450                    ftNode = findOrAddSubFtNode( (FeaturePropertyNode) propNode, featureStep );
451                }
452            }
453    
454            this.propertyPaths.add( propertyPath );
455            this.propertyNodes.add( propNode );
456            
457    //        // "equal" path is already registered, map this one to existing instance
458    //        if ( getPropertyNode( propertyPath ) == null ) {
459    //            this.propertyPaths.add( propertyPath );
460    //            this.propertyNodes.add( propNode );
461    //        }
462        }
463    
464        private PropertyNode createPropertyNode( FeatureTypeNode ftNode, MappedPropertyType pt ) {
465    
466            PropertyNode propNode = null;
467            if ( pt instanceof MappedSimplePropertyType ) {
468                String[] tableAliases = generateTableAliases( pt );
469                propNode = new SimplePropertyNode( (MappedSimplePropertyType) pt, ftNode, tableAliases );
470            } else if ( pt instanceof MappedGeometryPropertyType ) {
471                String[] tableAliases = generateTableAliases( pt );
472                propNode = new GeometryPropertyNode( (MappedGeometryPropertyType) pt, ftNode, tableAliases );
473            } else if ( pt instanceof MappedFeaturePropertyType ) {
474                String[] tableAliases = this.aliasGenerator.generateUniqueAliases( pt.getTableRelations().length - 1 );
475                propNode = new FeaturePropertyNode( ( (MappedFeaturePropertyType) pt ), ftNode, tableAliases );
476            } else {
477                assert ( false );
478            }
479            return propNode;
480        }
481    
482        /**
483         * Returns a {@link FeatureTypeNode} that select a child of the given {@link FeaturePropertyNode}.
484         * <p>
485         * If the step specifies a feature type alias (instead of the feature type name), the corresponding root feature
486         * type node is returned.
487         * 
488         * @param propNode
489         * @param featureStep
490         */
491        private FeatureTypeNode findOrAddSubFtNode( FeaturePropertyNode propNode, QualifiedName featureStep ) {
492    
493            FeatureTypeNode childNode = null;
494    
495            // check if step specifies an alias -> use corresponding root feature node then
496            if ( featureStep.getLocalName().startsWith( "$" ) ) {
497                String alias = featureStep.getLocalName().substring( 1 );
498                FeatureTypeNode [] childNodes = propNode.getFeatureTypeNodes();
499                for ( FeatureTypeNode node : childNodes ) {
500                    if (alias.equals( node.getFtAlias() )) {
501                        childNode = node;
502                        break;
503                    }
504                }
505                if (childNode == null) {
506                    childNode = this.ftAliasToRootFtNode.get( alias );
507                    propNode.addFeatureTypeNode( childNode );
508                }            
509            } else {
510                FeatureTypeNode [] subFtNodes = propNode.getFeatureTypeNodes();
511                for ( FeatureTypeNode node : subFtNodes ) {
512                    if (node.getFeatureType().getName().equals( featureStep )) {
513                        childNode = node;
514                        break;
515                    }
516                }
517                if (childNode == null) {
518                    MappedFeatureType subFt = this.roots[0].getFeatureType().getGMLSchema().getFeatureType( featureStep );
519                    String tableAlias = this.aliasGenerator.generateUniqueAlias();
520                    childNode = new FeatureTypeNode( subFt, null, tableAlias );
521                    propNode.addFeatureTypeNode( childNode );
522                }
523            }
524    
525            return childNode;
526        }
527    
528        // private FeaturePropertyNode createFeaturePropertyNode( FeatureTypeNode ftNode, MappedFeaturePropertyType pt) {
529        //
530        // // MappedFeatureType[] allowedTypes = pt.getFeatureTypeReference().getFeatureType().getConcreteSubstitutions();
531        // // QualifiedName givenTypeName = propertyPath.getStep( step + 1 ).getPropertyName();
532        // // MappedFeatureType givenType = null;
533        //
534        //
535        // }
536    
537        // private void addPathFragment( FeatureTypeNode ftNode, PropertyPath propertyPath, int startStep ) {
538        //
539        // for ( int step = startStep; step < propertyPath.getSteps(); step += 2 ) {
540        // LOG.logDebug( "Looking up property: " + propertyPath.getStep( step ).getPropertyName() );
541        // MappedPropertyType pt = getPropertyType( ftNode.getFeatureType(), propertyPath.getStep( step ) );
542        // if ( pt instanceof MappedSimplePropertyType ) {
543        // addSimplePropertyNode( ftNode, (MappedSimplePropertyType) pt, propertyPath, step );
544        // break;
545        // } else if ( pt instanceof MappedGeometryPropertyType ) {
546        // addGeometryPropertyNode( ftNode, (MappedGeometryPropertyType) pt, propertyPath, step );
547        // break;
548        // } else if ( pt instanceof MappedFeaturePropertyType ) {
549        // MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) pt;
550        // ftNode = addFeaturePropertyNode( ftNode, featurePT, propertyPath, step );
551        // } else {
552        // assert ( false );
553        // }
554        // }
555        // }
556    
557        /**
558         * Returns the {@link MappedPropertyType} for the given {@link MappedFeatureType} that matches the given
559         * {@link PropertyPathStep}.
560         * 
561         * @param ft
562         * @param step
563         * @return matching property type or null, if none exists
564         */
565        private MappedPropertyType getPropertyType( MappedFeatureType ft, PropertyPathStep step ) {
566    
567            MappedPropertyType pt = null;
568            QualifiedName name = step.getPropertyName();
569    
570            if ( step instanceof AttributeStep ) {
571                // TODO remove handling of gml:id here (after adaptation of feature model)
572                if ( CommonNamespaces.GMLNS.equals( name.getNamespace() ) && "id".equals( name.getLocalName() ) ) {
573                    MappedGMLId gmlId = ft.getGMLId();
574                    pt = new MappedSimplePropertyType( name, Types.VARCHAR, 1, 1, false, null, gmlId.getIdFields()[0] );
575                }
576            } else {
577                // normal property (not gml:id)
578                pt = (MappedPropertyType) ft.getProperty( name );
579            }
580    
581            return pt;
582        }
583    
584        // private void addSimplePropertyNode( FeatureTypeNode featureTypeNode, MappedSimplePropertyType propertyType,
585        // PropertyPath propertyPath, int step ) {
586        //
587        // assert ( step == propertyPath.getSteps() - 1 );
588        // String[] tableAliases = generateTableAliases( propertyType );
589        // PropertyNode propertyNode = new SimplePropertyNode( propertyType, featureTypeNode, tableAliases );
590        // this.propertyPaths.add( propertyPath );
591        // this.propertyNodes.add( propertyNode );
592        // featureTypeNode.addPropertyNode( propertyNode );
593        // }
594        //
595        // private void addGeometryPropertyNode( FeatureTypeNode featureTypeNode, MappedGeometryPropertyType propertyType,
596        // PropertyPath propertyPath, int step ) {
597        //
598        // assert ( step == propertyPath.getSteps() - 1 );
599        // String[] tableAliases = generateTableAliases( propertyType );
600        // PropertyNode propertyNode = new GeometryPropertyNode( propertyType, featureTypeNode, tableAliases );
601        // this.propertyPaths.add( propertyPath );
602        // this.propertyNodes.add( propertyNode );
603        // featureTypeNode.addPropertyNode( propertyNode );
604        // }
605        //
606        // private FeatureTypeNode addFeaturePropertyNode( FeatureTypeNode ftNode, MappedFeaturePropertyType pt,
607        // PropertyPath propertyPath, int step ) {
608        //
609        // assert ( step < propertyPath.getSteps() - 1 );
610        // MappedFeatureType[] allowedTypes = pt.getFeatureTypeReference().getFeatureType().getConcreteSubstitutions();
611        // QualifiedName givenTypeName = propertyPath.getStep( step + 1 ).getPropertyName();
612        // MappedFeatureType givenType = null;
613        //
614        // for ( int i = 0; i < allowedTypes.length; i++ ) {
615        // if ( allowedTypes[i].getName().equals( givenTypeName ) ) {
616        // givenType = allowedTypes[i];
617        // break;
618        // }
619        // }
620        // assert ( givenType != null );
621        //
622        // // TODO make proper
623        // String[] tableAliases = this.aliasGenerator.generateUniqueAliases( pt.getTableRelations().length - 1 );
624        // String tableAlias = this.aliasGenerator.generateUniqueAlias();
625        // FeatureTypeNode childFeatureTypeNode = new FeatureTypeNode( givenType, null, tableAlias );
626        //
627        // FeatureType ft = pt.getFeatureTypeReference().getFeatureType();
628        // LOG.logDebug( "featureType: " + ft.getName() );
629        //
630        // PropertyNode propertyNode = new FeaturePropertyNode( pt, ftNode, tableAliases, childFeatureTypeNode );
631        // // this.propertyPaths.add (propertyPath);
632        // // this.propertyNodes.add (propertyNode);
633        // ftNode.addPropertyNode( propertyNode );
634        // return childFeatureTypeNode;
635        // }
636    
637        private String[] generateTableAliases( MappedPropertyType pt ) {
638            String[] aliases = null;
639            TableRelation[] relations = pt.getTableRelations();
640            if ( relations != null ) {
641                aliases = new String[relations.length];
642                for ( int i = 0; i < aliases.length; i++ ) {
643                    aliases[i] = this.aliasGenerator.generateUniqueAlias();
644                }
645            }
646            return aliases;
647        }
648    
649        @Override
650        public String toString() {
651            StringBuffer sb = new StringBuffer();
652            for ( FeatureTypeNode root : this.roots ) {
653                sb.append( root.toString( "" ) );
654                sb.append( '\n' );
655            }
656            return sb.toString();
657        }
658    }