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