001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }