001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/model/feature/GMLFeatureCollectionDocument.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.model.feature;
037    
038    import static org.deegree.framework.xml.XMLTools.getChildElements;
039    
040    import java.io.InputStream;
041    import java.net.URI;
042    import java.net.URISyntaxException;
043    import java.util.ArrayList;
044    import java.util.Collection;
045    import java.util.HashMap;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.Map;
049    
050    import org.deegree.datatypes.QualifiedName;
051    import org.deegree.framework.log.ILogger;
052    import org.deegree.framework.log.LoggerFactory;
053    import org.deegree.framework.util.HttpUtils;
054    import org.deegree.framework.util.IDGenerator;
055    import org.deegree.framework.util.StringTools;
056    import org.deegree.framework.xml.ElementList;
057    import org.deegree.framework.xml.XMLParsingException;
058    import org.deegree.framework.xml.XMLTools;
059    import org.deegree.model.feature.schema.GMLSchema;
060    import org.deegree.model.feature.schema.GMLSchemaDocument;
061    import org.deegree.ogcbase.CommonNamespaces;
062    import org.w3c.dom.Element;
063    import org.w3c.dom.Text;
064    
065    /**
066     * Parser and wrapper class for GML feature collections.
067     * <p>
068     * Extends {@link GMLFeatureDocument}, as a feature collection is a feature in the GML type hierarchy.
069     * <p>
070     * 
071     * TODO Remove hack for xlinked feature members (should be easy after fixing model package).
072     * 
073     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
074     * @author last edited by: $Author: apoth $
075     * @version $Revision: 30817 $, $Date: 2011-05-16 10:14:00 +0200 (Mo, 16 Mai 2011) $
076     * 
077     * @see GMLFeatureDocument
078     */
079    public class GMLFeatureCollectionDocument extends GMLFeatureDocument {
080    
081        private static final long serialVersionUID = -6923435144671685710L;
082    
083        private static final ILogger LOG = LoggerFactory.getLogger( GMLFeatureCollectionDocument.class );
084    
085        private Collection<String> xlinkedMembers = new ArrayList<String>();
086    
087        private boolean keepCollectionName = false;
088    
089        /**
090         * Creates a new instance of <code>GMLFeatureCollectionDocument</code>.
091         * <p>
092         * Simple types encountered during parsing are "guessed", i.e. the parser tries to convert the values to double,
093         * integer, calendar, etc. However, this may lead to unwanted results, e.g. a property value of "054604" is
094         * converted to "54604".
095         * </p>
096         * <p>
097         * Note, the featurecollection Document created with this constructor will return wfs-1.1 bound FeatureCollections.
098         * If you want to return the same namespace bound feature collection as the incoming feature collection, please use
099         * the {@link #GMLFeatureCollectionDocument(boolean, boolean)}
100         * </p>
101         */
102        public GMLFeatureCollectionDocument() {
103            super();
104        }
105    
106        /**
107         * Creates a new instance of <code>GMLFeatureCollectionDocument</code>.
108         * <p>
109         * Note, the featurecollection Document created with this constructor will return wfs-1.1 bound FeatureCollections.
110         * If you want to return the same namespace bound feature collection as the incoming feature collection, please use
111         * the {@link #GMLFeatureCollectionDocument(boolean, boolean)}
112         * </p>
113         * 
114         * @param guessSimpleTypes
115         *            set to true, if simple types should be "guessed" during parsing
116         */
117        public GMLFeatureCollectionDocument( boolean guessSimpleTypes ) {
118            super( guessSimpleTypes );
119        }
120    
121        /**
122         * Creates a new instance of <code>GMLFeatureCollectionDocument</code>.
123         * <p>
124         * Instead of the other constructors, this one will be namespace aware of the incoming featureCollection. This
125         * means, that the incoming top root element will hold it's namespace binding and will not automatically be
126         * overwritten with the wfs:1.1 namespace binding.
127         * </p>
128         * 
129         * @param guessSimpleTypes
130         *            set to true, if simple types should be "guessed" during parsing
131         * @param keepCollectionName
132         *            if true, the returned FeatureCollection will have the same name as the incoming FeatureCollection
133         *            document. If set to false this constructor equals the {@link #GMLFeatureCollectionDocument(boolean)}.
134         */
135        public GMLFeatureCollectionDocument( boolean guessSimpleTypes, boolean keepCollectionName ) {
136            this( guessSimpleTypes );
137            this.keepCollectionName = keepCollectionName;
138        }
139    
140        /**
141         * Returns the object representation of the underlying feature collection document.
142         * 
143         * @return object representation of the underlying feature collection document.
144         * @throws XMLParsingException
145         */
146        public FeatureCollection parse()
147                                throws XMLParsingException {
148            readFeatureType( this.getRootElement() );
149            FeatureCollection fc = parse( this.getRootElement() );
150            resolveXLinkReferences();
151            addXLinkedMembers( fc );
152            return fc;
153        }
154    
155        /**
156         * @param rootElement
157         */
158        private void readFeatureType( Element rootElement ) {
159            boolean loaded = false;
160            try {
161                String v = XMLTools.getAttrValue( rootElement, URI.create( "http://www.w3.org/2001/XMLSchema-instance" ),
162                                                  "schemaLocation", null );
163                String[] tmp = StringTools.toArray( v, " ", false );
164                Map<URI, GMLSchema> gmlSchemaMap = new HashMap<URI, GMLSchema>();
165                for ( int i = 0; i < tmp.length; i++ ) {
166                    if ( tmp[i].indexOf( "=DescribeFeatureType" ) > -1 ) {
167                        // maybe should be de-activated if WFS delivering data is located behind a owsProxy
168                        GMLSchemaDocument schemaDoc = new GMLSchemaDocument();
169                        LOG.logInfo( "read schema from: ", tmp[i] );
170                        InputStream is = HttpUtils.performHttpGet( tmp[i], null, 60000, null, null, null ).getResponseBodyAsStream();
171                        schemaDoc.load( is, tmp[i] );
172                        GMLSchema schema = schemaDoc.parseGMLSchema();
173                        gmlSchemaMap.put( schemaDoc.getTargetNamespace(), schema );
174                        setSchemas( gmlSchemaMap );
175                        loaded = true;
176                    }
177                }
178            } catch ( Exception e ) {
179                e.printStackTrace();
180                LOG.logWarning( "can not create feature type from schema reference; going on with default/heuristic mechanism" );
181            }
182            if ( !loaded ) {
183                LOG.logWarning( "can not create feature type from schema reference; going on with default/heuristic mechanism" );
184            }
185        }
186    
187        /**
188         * Ugly hack that adds the "xlinked" feature members to the feature collection.
189         * 
190         * TODO remove this
191         * 
192         * @param fc
193         * @throws XMLParsingException
194         */
195        private void addXLinkedMembers( FeatureCollection fc )
196                                throws XMLParsingException {
197            Iterator<String> iter = this.xlinkedMembers.iterator();
198            while ( iter.hasNext() ) {
199                String fid = iter.next();
200                Feature feature = this.featureMap.get( fid );
201                if ( feature == null ) {
202                    String msg = Messages.format( "ERROR_XLINK_NOT_RESOLVABLE", fid );
203                    throw new XMLParsingException( msg );
204                }
205                fc.add( feature );
206            }
207        }
208    
209        /**
210         * Returns the object representation for the given feature collection element.
211         * 
212         * @return object representation for the given feature collection element.
213         * @throws XMLParsingException
214         */
215        private FeatureCollection parse( Element element )
216                                throws XMLParsingException {
217    
218            String fcId = parseFeatureId( element );
219            // generate id if necessary (use feature type name + a unique number as id)
220            if ( "".equals( fcId ) ) {
221                fcId = element.getLocalName();
222                fcId += IDGenerator.getInstance().generateUniqueID();
223            }
224    
225            String srsName = XMLTools.getNodeAsString( element, "gml:boundedBy/*[1]/@srsName", nsContext, null );
226    
227            ElementList el = XMLTools.getChildElements( element );
228            List<Feature> list = new ArrayList<Feature>( el.getLength() );
229    
230            for ( int i = 0; i < el.getLength(); i++ ) {
231                Feature member = null;
232                Element propertyElement = el.item( i );
233                String propertyName = propertyElement.getNodeName();
234    
235                if ( !propertyName.endsWith( "boundedBy" ) && !propertyName.endsWith( "name" )
236                     && !propertyName.endsWith( "description" ) ) {
237                    // the first child of a feature member must always be a feature
238                    // OR it's a featureMembers element (so we have MANY features)...
239                    ElementList featureList = getChildElements( el.item( i ) );
240                    for ( int k = 0; k < featureList.getLength(); ++k ) {
241                        Element featureElement = featureList.item( k );
242                        if ( featureElement == null ) {
243                            // check if feature content is xlinked
244                            // TODO remove this ugly hack
245                            Text xlinkHref = (Text) XMLTools.getNode( propertyElement, "@xlink:href/text()", nsContext );
246                            if ( xlinkHref == null ) {
247                                String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY", propertyName );
248                                throw new XMLParsingException( msg );
249                            }
250                            String href = xlinkHref.getData();
251                            if ( !href.startsWith( "#" ) ) {
252                                String msg = Messages.format( "ERROR_EXTERNAL_XLINK_NOT_SUPPORTED", href );
253                                throw new XMLParsingException( msg );
254                            }
255                            String fid = href.substring( 1 );
256                            this.xlinkedMembers.add( fid );
257                        } else {
258                            try {
259                                member = parseFeature( featureElement, srsName );
260                                list.add( member );
261                            } catch ( Exception e ) {
262                                throw new XMLParsingException( "Error creating feature instance from element '"
263                                                               + featureElement.getLocalName() + "': " + e.getMessage(), e );
264                            }
265                        }
266                    }
267                }
268            }
269    
270            Feature[] features = list.toArray( new Feature[list.size()] );
271            FeatureCollection fc = null;
272            if ( keepCollectionName ) {
273                String prefix = element.getPrefix();
274                String namespaceURI = element.getNamespaceURI();
275                if ( prefix != null && !"".equals( prefix.trim() ) ) {
276                    String tmp = element.lookupNamespaceURI( prefix );
277                    if ( tmp != null && !"".equals( tmp.trim() ) ) {
278                        namespaceURI = tmp;
279                    }
280                }
281                if ( namespaceURI == null || "".equals( namespaceURI.trim() )
282                     || CommonNamespaces.WFSNS.toASCIIString().equals( namespaceURI ) ) {
283                    fc = FeatureFactory.createFeatureCollection( fcId, features );
284                } else {
285                    QualifiedName name = null;
286                    URI ns = null;
287                    try {
288                        ns = new URI( namespaceURI );
289                        name = new QualifiedName( prefix, element.getLocalName(), ns );
290                    } catch ( URISyntaxException e ) {
291                        // a failure while creating the namespace uri, the name will be null and the
292                        // wfs:FeatureCollection
293                        // will be the default, just to be safe.
294                    }
295                    fc = FeatureFactory.createFeatureCollection( fcId, features, name );
296                }
297            } else {
298                // the old (default) behavior, just use the wfs-namespace for all feature collections.
299                fc = FeatureFactory.createFeatureCollection( fcId, features );
300            }
301            String nof = element.getAttribute( "numberOfFeatures" );
302            if ( nof == null ) {
303                nof = "" + features.length;
304            }
305            fc.setAttribute( "numberOfFeatures", nof );
306            return fc;
307        }
308    }