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 }