001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }