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