    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/datastore/PropertyPathResolver.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;
    037
    038    import java.util.ArrayList;
    039    import java.util.Collection;
    040    import java.util.Iterator;
    041    import java.util.LinkedHashMap;
    042    import java.util.List;
    043    import java.util.Map;
    044
    045    import org.deegree.datatypes.QualifiedName;
    046    import org.deegree.framework.log.ILogger;
    047    import org.deegree.framework.log.LoggerFactory;
    048    import org.deegree.i18n.Messages;
    049    import org.deegree.io.datastore.schema.MappedFeatureType;
    050    import org.deegree.io.datastore.schema.MappedPropertyType;
    051    import org.deegree.model.feature.schema.PropertyType;
    052    import org.deegree.ogcbase.ElementStep;
    053    import org.deegree.ogcbase.PropertyPath;
    054    import org.deegree.ogcbase.PropertyPathFactory;
    055    import org.deegree.ogcbase.PropertyPathStep;
    056    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
    057
    058    /**
    059     * Helper class that resolves {@link PropertyPath} instances (e.g. PropertyName elements in {@link GetFeature}) against
    060     * the property structure of {@link MappedFeatureType}s.
    061     *
    062     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
    063     * @author last edited by: $Author: mschneider $
    064     *
    065     * @version $Revision: 20808 $, $Date: 2009-11-16 14:03:13 +0100 (Mo, 16. Nov 2009) $
    066     */
    067    public class PropertyPathResolver {
    068
    069        private static final ILogger LOG = LoggerFactory.getLogger( PropertyPathResolver.class );
    070
    071        /**
    072         * Ensures that the requested property begins with a "feature type step" (this may also be an alias).
    073         * <p>
    074         * This is necessary, as section 7.4.2 of the Web Feature Implementation Specification 1.1.0 states: <br/>
    075         * <br/>
    076         * The first step of a relative location path <b>may</b> correspond to the root element of the feature property
    077         * being referenced <b>or</b> to the root element of the feature type with the next step corresponding to the root
    078         * element of the feature property being referenced.
    079         * </p>
    080         *
    081         * @param ft
    082         *            featureType that the requested properties refer to
    083         * @param alias
    084         *            alias for the feature type (may be null)
    085         * @param path
    086         *            requested property
    087         * @return normalized path, i.e. it begins with a feature type step
    088         */
    089        public static PropertyPath normalizePropertyPath( MappedFeatureType ft, String alias, PropertyPath path ) {
    090            QualifiedName ftName = ft.getName();
    091            QualifiedName firstStep = path.getStep( 0 ).getPropertyName();
    092            if ( !( firstStep.equals( ftName ) || ( alias != null && firstStep.equals( new QualifiedName( '$' + alias ) ) ) ) ) {
    093                if ( alias != null ) {
    094                    path.prepend( PropertyPathFactory.createPropertyPathStep( new QualifiedName( '$' + alias ) ) );
    095                } else {
    096                    path.prepend( PropertyPathFactory.createPropertyPathStep( ftName ) );
    097                }
    098            }
    099            return path;
    100        }
    101
    102        /**
    103         * Ensures that all requested properties begin with a feature type step or an alias.
    104         * <p>
    105         * If no properties are specified at all, a single PropertyPath entry is created that selects the whole feature
    106         * type.
    107         * </p>
    108         *
    109         * @param ft
    110         *            feature type that the requested properties refer to
    111         * @param alias
    112         *            alias for the feature type, may be null
    113         * @param paths
    114         *            requested properties, may not be null
    115         * @return normalized paths
    116         */
    117        public static PropertyPath[] normalizePropertyPaths( MappedFeatureType ft, String alias, PropertyPath[] paths ) {
    118
    119            PropertyPath[] normalizedPaths = new PropertyPath[paths.length];
    120
    121            for ( int i = 0; i < paths.length; i++ ) {
    122                normalizedPaths[i] = normalizePropertyPath( ft, alias, paths[i] );
    123            }
    124            if ( paths.length == 0 ) {
    125                QualifiedName firstStep = ft.getName();
    126                if ( alias != null ) {
    127                    firstStep = new QualifiedName( "$" + alias );
    128                }
    129                normalizedPaths = new PropertyPath[] { PropertyPathFactory.createPropertyPath( firstStep ) };
    130            }
    131            return normalizedPaths;
    132        }
    133
    134        /**
    135         * Ensures that all requested properties begin with a feature type (or alias) step.
    136         * <p>
    137         * If no properties are specified for a feature type, a single {@link PropertyPath} entry is created that selects
    138         * the whole feature type.
    139         * </p>
    140         *
    141         * @param fts
    142         *            feature types that the requested properties refer to
    143         * @param paths
    144         *            requested properties, may not be null
    145         * @return normalized paths
    146         * @throws PropertyPathResolvingException
    147         */
    148        public static List<PropertyPath>[] normalizePropertyPaths( MappedFeatureType[] fts, String[] ftAliases,
    149                                                                   PropertyPath[] paths )
    150                                throws PropertyPathResolvingException {
    151
    152            List<PropertyPath>[] propLists = new List[fts.length];
    153
    154            if ( fts.length == 1 ) {
    155                PropertyPath[] normalizedPaths = normalizePropertyPaths( fts[0], ftAliases != null ? ftAliases[0] : null,
    156                                                                         paths );
    157                List<PropertyPath> pathList = new ArrayList<PropertyPath>( normalizedPaths.length );
    158                for ( PropertyPath path : normalizedPaths ) {
    159                    pathList.add( path );
    160                }
    161                propLists[0] = pathList;
    162            } else {
    163                for ( PropertyPath path : paths ) {
    164                    QualifiedName firstStep = path.getStep( 0 ).getPropertyName();
    165                    int i = 0;
    166
    167                    if ( ftAliases == null ) {
    168                        for ( i = 0; i < fts.length; i++ ) {
    169                            if ( fts[i].getName().equals( firstStep ) ) {
    170                                break;
    171                            }
    172                        }
    173                    } else {
    174                        String localName = firstStep.getLocalName();
    175                        for ( i = 0; i < fts.length; i++ ) {
    176                            String fullAlias = '$' + ftAliases[i];
    177                            if ( fullAlias.equals( localName ) ) {
    178                                break;
    179                            }
    180                        }
    181                    }
    182
    183                    if ( i < fts.length ) {
    184                        List props = propLists[i];
    185                        if ( props == null ) {
    186                            props = new ArrayList<PropertyPath>();
    187                            propLists[i] = props;
    188                        }
    189                        props.add( path );
    190                    } else {
    191                        String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE5", path, firstStep );
    192                        throw new PropertyPathResolvingException( msg );
    193                    }
    194                }
    195
    196                for ( int i = 0; i < propLists.length; i++ ) {
    197                    List<PropertyPath> list = propLists[i];
    198                    if ( list == null ) {
    199                        list = new ArrayList<PropertyPath>( 1 );
    200                        propLists[i] = list;
    201                        if ( paths.length == 0 ) {
    202                            // only assume selection of every feature type if no feature type is
    203                            // selected explicitly
    204                            QualifiedName firstStep = fts[i].getName();
    205                            if ( ftAliases != null ) {
    206                                firstStep = new QualifiedName( "$" + ftAliases[i] );
    207                            }
    208                            PropertyPath ftSelect = PropertyPathFactory.createPropertyPath( firstStep );
    209                            list.add( ftSelect );
    210                        }
    211                    }
    212                }
    213            }
    214            return propLists;
    215        }
    216
    217        /**
    218         * Determines the properties of the given feature type that have to be fetched based on the requested property
    219         * paths.
    220         * <p>
    221         * Returns a helper <code>Map</code> that associates each (requested) property of the feature with the property
    222         * paths that request it. Note that the returned helper map may contain more properties than specified. This
    223         * behaviour is due to section 9.2 of the Web Feature Implementation Specification 1.1.0:
    224         * </p>
    225         * <p>
    226         * In the event that a WFS encounters a query that does not select all mandatory properties of a feature, the WFS
    227         * will internally augment the property name list to include all mandatory property names.
    228         * </p>
    229         * <p>
    230         * Note that every requested property path must begin with a step that selects the given feature type.
    231         * </p>
    232         *
    233         * @param ft
    234         *            feature type
    235         * @param alias
    236         *            alias for the feature type (may be null)
    237         * @param requestedPaths
    238         * @return <code>Map</code>, key class: <code>MappedPropertyType</code>, value class: <code>Collection</code> (of
    239         *         <code>PropertyPath</code> instances)
    240         * @throws PropertyPathResolvingException
    241         */
    242        public static Map<MappedPropertyType, Collection<PropertyPath>> determineFetchProperties(
    243                                                                                                  MappedFeatureType ft,
    244                                                                                                  String alias,
    245                                                                                                  PropertyPath[] requestedPaths )
    246                                throws PropertyPathResolvingException {
    247
    248            Map<MappedPropertyType, Collection<PropertyPath>> requestedMap = null;
    249            requestedMap = determineRequestedProperties( ft, alias, requestedPaths );
    250            if ( requestedPaths.length != 0 ) {
    251                // only augment mandatory properties, if any property is selected
    252                requestedMap = augmentFetchProperties( ft, alias, requestedMap );
    253            }
    254            return requestedMap;
    255        }
    256
    257        /**
    258         * Builds a map that associates each requested property of the feature with the property paths that request it. The
    259         * property path may well select a property of a subfeature, but the key values in the map are always (direct)
    260         * properties of the given feature type.
    261         *
    262         * @param ft
    263         *            feature type
    264         * @param alias
    265         *            alias for the feature type (may be null)
    266         * @param requestedPaths
    267         * @return map that associates each requested property with the property paths that request it
    268         * @throws PropertyPathResolvingException
    269         */
    270        private static Map<MappedPropertyType, Collection<PropertyPath>> determineRequestedProperties(
    271                                                                                                       MappedFeatureType ft,
    272                                                                                                       String alias,
    273
    274                                                                                                       PropertyPath[] requestedPaths )
    275                                throws PropertyPathResolvingException {
    276
    277            Map<MappedPropertyType, Collection<PropertyPath>> propertyMap = new LinkedHashMap<MappedPropertyType, Collection<PropertyPath>>();
    278
    279            LOG.logDebug( "Alias: " + alias );
    280            for ( int i = 0; i < requestedPaths.length; i++ ) {
    281                PropertyPath requestedPath = requestedPaths[i];
    282                QualifiedName firstStep = requestedPath.getStep( 0 ).getPropertyName();
    283                LOG.logDebug( "path " + i + ": " + requestedPaths[i] );
    284                if ( firstStep.equals( ft.getName() )
    285                     || ( alias != null && firstStep.equals( new QualifiedName( '$' + alias ) ) ) ) {
    286                    if ( requestedPath.getSteps() == 1 ) {
    287                        // path requests the whole feature
    288                        PropertyType[] allProperties = ft.getProperties();
    289                        for ( int j = 0; j < allProperties.length; j++ ) {
    290                            Collection<PropertyPath> paths = propertyMap.get( allProperties[j] );
    291                            if ( paths == null ) {
    292                                paths = new ArrayList<PropertyPath>();
    293                            }
    294                            PropertyPath newPropertyPath = PropertyPathFactory.createPropertyPath( ft.getName() );
    295                            newPropertyPath.append( PropertyPathFactory.createPropertyPathStep( allProperties[j].getName() ) );
    296                            paths.add( newPropertyPath );
    297                            propertyMap.put( (MappedPropertyType) allProperties[j], paths );
    298                        }
    299                    } else {
    300                        // path requests a certain property
    301                        QualifiedName propertyName = requestedPath.getStep( 1 ).getPropertyName();
    302                        PropertyType property = ft.getProperty( propertyName );
    303                        // quirk mode...
    304                        if ( property == null ) {
    305                            for ( PropertyType type : ft.getProperties() ) {
    306                                if ( type.getName().getLocalName().equals( propertyName.getLocalName() ) ) {
    307                                    property = type;
    308                                }
    309                            }
    310                        }
    311                        if ( property == null ) {
    312                            // workaround for gml:boundedBy
    313                            if ("boundedBy".equals (propertyName.getLocalName())) {
    314                                continue;
    315                            }
    316                            String msg = Messages.getMessage( "DATASTORE_NO_SUCH_PROPERTY2", requestedPath, ft.getName(),
    317                                                              propertyName );
    318                            throw new PropertyPathResolvingException( msg );
    319                        }
    320                        Collection<PropertyPath> paths = propertyMap.get( property );
    321                        if ( paths == null ) {
    322                            paths = new ArrayList<PropertyPath>();
    323                        }
    324                        paths.add( requestedPath );
    325                        propertyMap.put( (MappedPropertyType) property, paths );
    326                    }
    327                } else {
    328                    String msg = "Internal error in PropertyPathResolver: no property with name '" + requestedPath
    329                                 + "' in feature type '" + ft.getName() + "'.";
    330                    throw new PropertyPathResolvingException( msg );
    331                }
    332            }
    333            return propertyMap;
    334        }
    335
    336        /**
    337         * Returns an augmented version of the input map that contains additional entries for all mandatory properties of
    338         * the feature type.
    339         *
    340         * @param ft
    341         *            feature type
    342         * @param alias
    343         *            alias for the feature type (may be null)
    344         * @param requestedMap
    345         * @return augmented version of the input map
    346         */
    347        private static Map<MappedPropertyType, Collection<PropertyPath>> augmentFetchProperties(
    348                                                                                                 MappedFeatureType ft,
    349                                                                                                 String alias,
    350                                                                                                 Map<MappedPropertyType, Collection<PropertyPath>> requestedMap ) {
    351
    352            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    353                LOG.logDebug( "Properties to be fetched for feature type '" + ft.getName() + "' (alias=" + alias + "):" );
    354            }
    355
    356            Map<MappedPropertyType, Collection<PropertyPath>> augmentedMap = new LinkedHashMap<MappedPropertyType, Collection<PropertyPath>>();
    357            PropertyType[] allProperties = ft.getProperties();
    358            for ( int i = 0; i < allProperties.length; i++ ) {
    359                MappedPropertyType property = (MappedPropertyType) allProperties[i];
    360                Collection<PropertyPath> requestingPaths = requestedMap.get( property );
    361                if ( requestingPaths != null ) {
    362                    LOG.logDebug( "- " + property.getName() );
    363                    augmentedMap.put( property, requestingPaths );
    364                    for ( PropertyPath path : requestingPaths ) {
    365                        LOG.logDebug( "  - Requested by path: '" + path + "'" );
    366                    }
    367                } else if ( property.getMinOccurs() > 0 ) {
    368                    LOG.logDebug( "- " + property.getName() + " (augmented)" );
    369                    Collection<PropertyPath> mandatoryPaths = new ArrayList<PropertyPath>();
    370                    List<PropertyPathStep> stepList = new ArrayList<PropertyPathStep>( 2 );
    371                    stepList.add( new ElementStep( ft.getName() ) );
    372                    stepList.add( new ElementStep( property.getName() ) );
    373                    PropertyPath mandatoryPath = new PropertyPath( stepList );
    374                    mandatoryPaths.add( mandatoryPath );
    375                    augmentedMap.put( property, mandatoryPaths );
    376                }
    377            }
    378            return augmentedMap;
    379        }
    380
    381        /**
    382         * Determines the sub property paths that are needed to fetch the given property paths for the also given property.
    383         *
    384         * @param featureType
    385         * @param propertyPaths
    386         * @return sub property paths that are needed to fetch the given property paths
    387         */
    388        public static PropertyPath[] determineSubPropertyPaths( MappedFeatureType featureType,
    389                                                                Collection<PropertyPath> propertyPaths ) {
    390            Collection<PropertyPath> subPropertyPaths = new ArrayList<PropertyPath>();
    391
    392            Iterator<PropertyPath> iter = propertyPaths.iterator();
    393            while ( iter.hasNext() ) {
    394                PropertyPath path = iter.next();
    395                if ( path.getSteps() > 2 ) {
    396                    subPropertyPaths.add( PropertyPathFactory.createPropertyPath( path, 2, path.getSteps() ) );
    397                } else {
    398                    PropertyType[] subProperties = featureType.getProperties();
    399                    for ( int i = 0; i < subProperties.length; i++ ) {
    400                        PropertyPath subPropertyPath = PropertyPathFactory.createPropertyPath( featureType.getName() );
    401                        subPropertyPath.append( PropertyPathFactory.createPropertyPathStep( subProperties[i].getName() ) );
    402                        subPropertyPaths.add( subPropertyPath );
    403                    }
    404                }
    405            }
    406
    407            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    408                LOG.logDebug( "Original property paths:" );
    409                for ( PropertyPath path : propertyPaths ) {
    410                    LOG.logDebug( "- '" + path + "'" );
    411                }
    412                LOG.logDebug( "Sub feature property paths:" );
    413                for ( PropertyPath path : subPropertyPaths ) {
    414                    LOG.logDebug( "- '" + path + "'" );
    415                }
    416            }
    417            PropertyPath[] subPaths = subPropertyPaths.toArray( new PropertyPath[subPropertyPaths.size()] );
    418            return subPaths;
    419        }
    420    }