001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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.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: 24932 $, $Date: 2010-06-17 20:16:34 +0200 (Do, 17 Jun 2010) $
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                        GMLSchemaDocument schemaDoc = new GMLSchemaDocument();
168                        LOG.logInfo( "read schema from: ", tmp[i] );
169                        InputStream is = HttpUtils.performHttpGet( tmp[i], null, 60000, null, null, null ).getResponseBodyAsStream();
170                        schemaDoc.load( is, tmp[i] );
171                        GMLSchema schema = schemaDoc.parseGMLSchema();
172                        gmlSchemaMap.put( schemaDoc.getTargetNamespace(), schema );
173                        setSchemas( gmlSchemaMap );
174                        loaded = true;
175                    }
176                }
177            } catch ( Exception e ) {
178                e.printStackTrace();
179                LOG.logWarning( "can not create feature type from schema reference; going on with default/heuristic mechanism" );
180            }
181            if ( !loaded ) {
182                LOG.logWarning( "can not create feature type from schema reference; going on with default/heuristic mechanism" );
183            }
184        }
185    
186        /**
187         * Ugly hack that adds the "xlinked" feature members to the feature collection.
188         * 
189         * TODO remove this
190         * 
191         * @param fc
192         * @throws XMLParsingException
193         */
194        private void addXLinkedMembers( FeatureCollection fc )
195                                throws XMLParsingException {
196            Iterator<String> iter = this.xlinkedMembers.iterator();
197            while ( iter.hasNext() ) {
198                String fid = iter.next();
199                Feature feature = this.featureMap.get( fid );
200                if ( feature == null ) {
201                    String msg = Messages.format( "ERROR_XLINK_NOT_RESOLVABLE", fid );
202                    throw new XMLParsingException( msg );
203                }
204                fc.add( feature );
205            }
206        }
207    
208        /**
209         * Returns the object representation for the given feature collection element.
210         * 
211         * @return object representation for the given feature collection element.
212         * @throws XMLParsingException
213         */
214        private FeatureCollection parse( Element element )
215                                throws XMLParsingException {
216    
217            String fcId = parseFeatureId( element );
218            // generate id if necessary (use feature type name + a unique number as id)
219            if ( "".equals( fcId ) ) {
220                fcId = element.getLocalName();
221                fcId += IDGenerator.getInstance().generateUniqueID();
222            }
223    
224            String srsName = XMLTools.getNodeAsString( element, "gml:boundedBy/*[1]/@srsName", nsContext, null );
225    
226            ElementList el = XMLTools.getChildElements( element );
227            List<Feature> list = new ArrayList<Feature>( el.getLength() );
228    
229            for ( int i = 0; i < el.getLength(); i++ ) {
230                Feature member = null;
231                Element propertyElement = el.item( i );
232                String propertyName = propertyElement.getNodeName();
233    
234                if ( !propertyName.endsWith( "boundedBy" ) && !propertyName.endsWith( "name" )
235                     && !propertyName.endsWith( "description" ) ) {
236                    // the first child of a feature member must always be a feature
237                    // OR it's a featureMembers element (so we have MANY features)...
238                    ElementList featureList = getChildElements( el.item( i ) );
239                    for ( int k = 0; k < featureList.getLength(); ++k ) {
240                        Element featureElement = featureList.item( k );
241                        if ( featureElement == null ) {
242                            // check if feature content is xlinked
243                            // TODO remove this ugly hack
244                            Text xlinkHref = (Text) XMLTools.getNode( propertyElement, "@xlink:href/text()", nsContext );
245                            if ( xlinkHref == null ) {
246                                String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY", propertyName );
247                                throw new XMLParsingException( msg );
248                            }
249                            String href = xlinkHref.getData();
250                            if ( !href.startsWith( "#" ) ) {
251                                String msg = Messages.format( "ERROR_EXTERNAL_XLINK_NOT_SUPPORTED", href );
252                                throw new XMLParsingException( msg );
253                            }
254                            String fid = href.substring( 1 );
255                            this.xlinkedMembers.add( fid );
256                        } else {
257                            try {
258                                member = parseFeature( featureElement, srsName );
259                                list.add( member );
260                            } catch ( Exception e ) {
261                                throw new XMLParsingException( "Error creating feature instance from element '"
262                                                               + featureElement.getLocalName() + "': " + e.getMessage(), e );
263                            }
264                        }
265                    }
266                }
267            }
268    
269            Feature[] features = list.toArray( new Feature[list.size()] );
270            FeatureCollection fc = null;
271            if ( keepCollectionName ) {
272                String prefix = element.getPrefix();
273                String namespaceURI = element.getNamespaceURI();
274                if ( prefix != null && !"".equals( prefix.trim() ) ) {
275                    String tmp = element.lookupNamespaceURI( prefix );
276                    if ( tmp != null && !"".equals( tmp.trim() ) ) {
277                        namespaceURI = tmp;
278                    }
279                }
280                if ( namespaceURI == null || "".equals( namespaceURI.trim() )
281                     || CommonNamespaces.WFSNS.toASCIIString().equals( namespaceURI ) ) {
282                    fc = FeatureFactory.createFeatureCollection( fcId, features );
283                } else {
284                    QualifiedName name = null;
285                    URI ns = null;
286                    try {
287                        ns = new URI( namespaceURI );
288                        name = new QualifiedName( prefix, element.getLocalName(), ns );
289                    } catch ( URISyntaxException e ) {
290                        // a failure while creating the namespace uri, the name will be null and the
291                        // wfs:FeatureCollection
292                        // will be the default, just to be safe.
293                    }
294                    fc = FeatureFactory.createFeatureCollection( fcId, features, name );
295                }
296            } else {
297                // the old (default) behavior, just use the wfs-namespace for all feature collections.
298                fc = FeatureFactory.createFeatureCollection( fcId, features );
299            }
300            String nof = element.getAttribute( "numberOfFeatures" );
301            if ( nof == null ) {
302                nof = "" + features.length;
303            }
304            fc.setAttribute( "numberOfFeatures", nof );
305            return fc;
306        }
307    }