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