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 }