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 }