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 }