001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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 }