/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2015, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.feature.xml.jaxp; import java.io.IOException; import java.lang.reflect.Array; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.logging.Level; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.apache.cxf.staxutils.StaxUtils; import org.apache.sis.feature.FeatureExt; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.storage.DataStoreException; import org.apache.sis.xml.MarshallerPool; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.util.NamesExt; import org.opengis.util.GenericName; import org.geotoolkit.feature.xml.Utils; import org.geotoolkit.feature.xml.XmlFeatureWriter; import org.geotoolkit.geometry.isoonjts.JTSUtils; import org.geotoolkit.gml.JTStoGeometry; import org.geotoolkit.gml.xml.AbstractCurve; import org.geotoolkit.gml.xml.AbstractGeometry; import org.geotoolkit.gml.xml.AbstractSurface; import org.geotoolkit.gml.xml.CurveProperty; import org.geotoolkit.gml.xml.GMLMarshallerPool; import org.geotoolkit.gml.xml.MultiCurve; import org.geotoolkit.gml.xml.MultiSurface; import org.geotoolkit.gml.xml.SurfaceProperty; import org.geotoolkit.gml.xml.v321.AbstractGeometryType; import org.geotoolkit.gml.xml.v321.AbstractSolidType; import org.geotoolkit.gml.xml.v321.GeometryPropertyType; import org.geotoolkit.gml.xml.v321.MultiGeometryType; import org.geotoolkit.gml.xml.v321.MultiPointType; import org.geotoolkit.gml.xml.v321.MultiSolidType; import org.geotoolkit.gml.xml.v321.PointPropertyType; import org.geotoolkit.gml.xml.v321.PointType; import org.geotoolkit.gml.xml.v321.SolidPropertyType; import org.geotoolkit.internal.jaxb.JTSWrapperMarshallerPool; import org.geotoolkit.internal.jaxb.ObjectFactory; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.util.ObjectConverters; import org.geotoolkit.feature.xml.GMLConvention; import org.geotoolkit.xml.StaxStreamWriter; import org.opengis.feature.Attribute; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.FeatureType; import org.opengis.feature.Property; import org.opengis.feature.PropertyNotFoundException; import org.opengis.feature.PropertyType; import org.opengis.filter.identity.Identifier; import org.opengis.geometry.Envelope; import org.opengis.geometry.Geometry; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.FactoryException; import org.w3c.dom.Document; /** * Handles writing process of features using JAXP. The {@link #dispose()} method MUST be * called in order to release some resources. * * @author Guilhem Legal (Geomatys) */ public class JAXPStreamFeatureWriter extends StaxStreamWriter implements XmlFeatureWriter { private static final String XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"; /** * The pool of marshallers used for marshalling geometries. */ @Deprecated private static final MarshallerPool GML_31_POOL = JTSWrapperMarshallerPool.getInstance(); private static final MarshallerPool GML_32_POOL = GMLMarshallerPool.getInstance(); /** * Object factory to build a geometry. */ private static final ObjectFactory OBJECT_FACTORY = new ObjectFactory(); private static final org.geotoolkit.gml.xml.v321.ObjectFactory GML32_FACTORY = new org.geotoolkit.gml.xml.v321.ObjectFactory(); protected String schemaLocation; private final String gmlVersion; private final String wfsVersion; private final String wfsNamespace; private final String wfsLocation; private final String gmlNamespace; private final String gmlLocation; private static final DateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); //automatic id increment for geometries id private int gidInc = 0; public JAXPStreamFeatureWriter() { this("3.1.1", "1.1.0", null); } public JAXPStreamFeatureWriter(final String gmlVersion, final String wfsVersion, final Map<String, String> schemaLocations) { this.gmlVersion = gmlVersion; this.wfsVersion = wfsVersion; if ("2.0.0".equals(wfsVersion)) { wfsNamespace = "http://www.opengis.net/wfs/2.0"; wfsLocation = "http://schemas.opengis.net/wfs/2.0/wfs.xsd"; } else { wfsNamespace = "http://www.opengis.net/wfs"; wfsLocation = "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"; } if ("3.2.1".equals(gmlVersion)) { gmlNamespace = "http://www.opengis.net/gml/3.2"; gmlLocation = "http://schemas.opengis.net/gml/3.2.1/gml.xsd"; } else { gmlNamespace = "http://www.opengis.net/gml"; gmlLocation = "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd"; } if (schemaLocations != null && schemaLocations.size() > 0) { final StringBuilder sb = new StringBuilder(); for (Entry<String, String> entry : schemaLocations.entrySet()) { sb.append(entry.getKey()).append(' ').append(entry.getValue()).append(' '); } // add wfs schema Location sb.append(wfsNamespace).append(' ').append(wfsLocation).append(' '); sb.append(gmlNamespace).append(' ').append(gmlLocation).append(' '); schemaLocation = sb.toString(); } } public JAXPStreamFeatureWriter(final Map<String, String> schemaLocations) { this("3.1.1", "1.1.0", schemaLocations); } /** * Dispose the allocated resources. <strong>Must</strong> be called when closing the feautre writer. * * @throws IOException * @throws XMLStreamException */ @Override public void dispose() throws IOException, XMLStreamException{ super.dispose(); } /** * {@inheritDoc} */ @Override public void write(final Object candidate, final Object output) throws IOException, XMLStreamException, DataStoreException { this.write(candidate, output, null); } /** * {@inheritDoc} */ @Override public void write(final Object candidate, final Object output, final Integer nbMatched) throws IOException, XMLStreamException, DataStoreException { setOutput(output); if (candidate instanceof Feature) { writeFeature((Feature) candidate,true); } else if (candidate instanceof FeatureCollection) { writeFeatureCollection((FeatureCollection) candidate,false, nbMatched); } else { throw new IllegalArgumentException("The given object is not a Feature or a" + " FeatureCollection: "+ candidate); } } /** * Write the feature into the stream. * * @param feature The feature * @param root * @throws XMLStreamException */ private void writeFeature(final Feature feature, final boolean root) throws XMLStreamException { //reset geometry id increment gidInc = 0; //the root element of the xml document (type of the feature) final FeatureType type = feature.getType(); final GenericName typeName = type.getName(); final String namespace = NamesExt.getNamespace(typeName); final String localPart = typeName.tip().toString(); final String gmlid = getId(feature, null); if (namespace != null && !namespace.isEmpty()) { final Prefix prefix = getPrefix(namespace); writer.writeStartElement(prefix.prefix, localPart, namespace); if (gmlid != null) { writer.writeAttribute("gml", gmlNamespace, "id", gmlid); } if (prefix.unknow && !root) { writer.writeNamespace(prefix.prefix, namespace); } if (root) { writer.writeNamespace("gml", gmlNamespace); writer.writeNamespace("xsi", XSI_NAMESPACE); if (!namespace.equals(gmlNamespace)) { writer.writeNamespace(prefix.prefix, namespace); } } } else { writer.writeStartElement(localPart); if (gmlid != null) { writer.writeAttribute("gml", gmlNamespace, "id", gmlid); } } writeComplexProperties(feature, gmlid); writer.writeEndElement(); writer.flush(); } private static String getId(Feature att, String fallback){ final Identifier attId; try { attId = FeatureExt.getId(att); } catch(PropertyNotFoundException ex) { return fallback; } if(attId==null) return fallback; final Object id = attId.getID(); if(id==null) return fallback; if(id instanceof String){ if(((String)id).isEmpty()){ return fallback; }else{ return ((String)id).replace(':', '_'); } }else{ return String.valueOf(id).replace(':', '_'); } } /** * Write atribute properties. * If we found a nil reason than return is true * * TODO this is not a perfect way to know if a propery is null. * but if we don't declare the property then we don't know the reason either... * * * @param feature * @return * @throws XMLStreamException */ private boolean writeAttributeProperties(final Feature feature) throws XMLStreamException { final FeatureType type = feature.getType(); boolean nil = false; //write properties in the type order for(final PropertyType desc : type.getProperties(true)){ if(AttributeConvention.contains(desc.getName())) continue; if(!isAttributeProperty(desc.getName())) continue; if(desc.getName().tip().toString().equals("@id")) { //gml id has already been written continue; } Object value = feature.getPropertyValue(desc.getName().toString()); final GenericName nameA = desc.getName(); String nameProperty = nameA.tip().toString(); String namespaceProperty = NamesExt.getNamespace(nameA); //remove the @ nameProperty = nameProperty.substring(1); nil |= "nil".equals(nameProperty) && Boolean.TRUE.equals(value); if(value instanceof Boolean) { value = (Boolean)value ? "1" : "0"; } String valueStr = Utils.getStringValue(value); if (valueStr != null) { if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeAttribute(namespaceProperty, nameProperty, valueStr); } else { writer.writeAttribute(nameProperty, valueStr); } } } return nil; } /** * Test if the feature is nill. * * @return null if the object is not nill * Boolean.TRUE if the object is nill without reason * String if the object is nill with a reason */ private static Object isNill(Feature feature){ try { if(Boolean.TRUE.equals(feature.getPropertyValue("@nil"))){ try { Object reason = feature.getPropertyValue("@nilReason"); if (reason!=null) { return Utils.getStringValue(reason); } }catch(PropertyNotFoundException ex){ } return true; } } catch(PropertyNotFoundException ex) { return null; } return null; } private void writeComplexProperties(final Feature feature, String id) throws XMLStreamException { final boolean isNil = writeAttributeProperties(feature); if(isNil) return; //write properties in the type order for(PropertyType pt : feature.getType().getProperties(true)){ final Object value = feature.getPropertyValue(pt.getName().toString()); final Collection<Object> values; if(value instanceof Collection){ values = (Collection<Object>) value; }else{ values = Collections.singleton(value); } if(!values.isEmpty()){ for (Object a : values) { writeProperty(feature,pt,a,id); } }else if(Utils.isNillable(pt)){ //we must have at least one tag with nil=1 final GenericName nameA = pt.getName(); final String namespaceProperty = NamesExt.getNamespace(nameA); final String nameProperty = nameA.tip().toString(); if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } writer.writeAttribute("http://www.w3.org/2001/XMLSchema-instance", "nil", "1"); writer.writeEndElement(); } } } private void writeCharacteristics(Attribute att) throws XMLStreamException { final Iterator<Attribute> ite = att.characteristics().values().iterator(); while (ite.hasNext()) { final Attribute chara = ite.next(); final GenericName name = chara.getName(); final String namespace = NamesExt.getNamespace(name); String localPart = name.tip().toString(); if(localPart.startsWith("@")){ //remove the @ localPart = localPart.substring(1); } Object value = chara.getValue(); if(value instanceof Boolean) { value = (Boolean)value ? "1" : "0"; } if (value!=null) { writer.writeAttribute(namespace, localPart,ObjectConverters.convert(value, String.class)); } } } private void writeProperty(Feature parent, PropertyType typeA, Object valueA, String id) throws XMLStreamException{ final FeatureType parentType = parent.getType(); final GenericName nameA = typeA.getName(); final String nameProperty = nameA.tip().toString(); String namespaceProperty = NamesExt.getNamespace(nameA); final boolean hasChars = typeA instanceof AttributeType && !((AttributeType)typeA).characteristics().isEmpty(); if(isAttributeProperty(nameA)) return; //TODO : search for link operation which match // if(!isSubstitute){ // for(PropertyDescriptor desc : parentType.getDescriptors()){ // final PropertyType pt = desc.getType(); // if(pt instanceof Link && ((AliasOperation)pt).getRefName().equals(nameA)){ // //possible substitute group // Property p = parent.getProperty(desc.getName()); // if(p!=null){ // final PropertyType originalType = parent.getType().getDescriptor(a.getName()).getType(); // if(p.getType().equals(originalType)){ // //substitute has exactly the same type // //we favorite the non alias type // break; // } // // //valid substitute, we write it instead of the current property // writeProperty(parent, parent.getType(),p, true, id); // return; // } // } // } // } if (typeA instanceof FeatureAssociationRole && valueA instanceof Feature) { final FeatureAssociationRole far = (FeatureAssociationRole) typeA; final Feature ca = (Feature)valueA; //write feature if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } //nill case final Object isNil = isNill(ca); if (isNil != null) { writeAttributeProperties(ca); writer.writeEndElement(); return; } /* Note : the GML 3.2 identifier element is this only one which does not follow the OGC 'PropertyType' pattern and is not encapsulated. Note : if more cases are found, a more generic approach should be used. */ boolean encapsulate = !"identifier".equals(ca.getType().getName().tip().toString()); if (encapsulate) { //we need to encapsulate type final FeatureType valueType = far.getValueType(); final String encapName = Utils.getNameWithoutTypeSuffix(valueType.getName().tip().toString()); if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, encapName); } else { writer.writeStartElement(encapName); } writeComplexProperties(ca, getId(ca, id)); //close encapsulation writer.writeEndElement(); } else { writeComplexProperties(ca, getId(ca, id)); } writer.writeEndElement(); } else if (valueA instanceof Collection && !(AttributeConvention.isGeometryAttribute(typeA))) { for (Object value : (Collection)valueA) { if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } if(hasChars) writeCharacteristics((Attribute) parent.getProperty(nameA.toString())); writer.writeCharacters(Utils.getStringValue(value)); writer.writeEndElement(); } } else if (valueA != null && valueA.getClass().isArray() && !(AttributeConvention.isGeometryAttribute(typeA))) { final int length = Array.getLength(valueA); for (int i = 0; i < length; i++){ if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } if(hasChars) writeCharacteristics((Attribute) parent.getProperty(nameA.toString())); final Object value = Array.get(valueA, i); final String textValue; if (value != null && value.getClass().isArray()) { // matrix final StringBuilder sb = new StringBuilder(); final int length2 = Array.getLength(value); for (int j = 0; j < length2; j++) { final Object subValue = Array.get(value, j); sb.append(Utils.getStringValue(subValue)).append(" "); } textValue = sb.toString(); } else { textValue = Utils.getStringValue(value); } writer.writeCharacters(textValue); writer.writeEndElement(); } } else if (valueA instanceof Map && !(AttributeConvention.isGeometryAttribute(typeA))) { final Map<?,?> map = (Map)valueA; for (Entry<?,?> entry : map.entrySet()) { if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } if(hasChars) writeCharacteristics((Attribute) parent.getProperty(nameA.toString())); final Object key = entry.getKey(); if (key != null) { writer.writeAttribute("name", (String)key); } writer.writeCharacters(Utils.getStringValue(entry.getValue())); writer.writeEndElement(); } } else if (!(AttributeConvention.isGeometryAttribute(typeA))) { if(valueA instanceof Document){ //special case for xml documents final Document doc = (Document) valueA; StaxUtils.writeElement(doc.getDocumentElement(), writer, false); }else{ //simple type String value = (valueA instanceof Property) ? null : Utils.getStringValue(valueA); if ((nameProperty.equals("name") || nameProperty.equals("description")) && !gmlNamespace.equals(namespaceProperty)) { namespaceProperty = gmlNamespace; LOGGER.finer("the property name and description of a feature must have the GML namespace"); } if (valueA instanceof Feature || value != null) { if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } if(hasChars) writeCharacteristics((Attribute) parent.getProperty(nameA.toString())); if(valueA instanceof Feature){ //some types, like Observation & Measurement have Object types which can be //properties again, we ensure to write then as proper xml tags final Feature prop = (Feature) valueA; final GenericName propName = prop.getType().getName(); final String namespaceURI = NamesExt.getNamespace(propName); final String localPart = Utils.getNameWithoutTypeSuffix(propName.tip().toString()); if (namespaceURI != null && !namespaceURI.isEmpty()) { writer.writeStartElement(namespaceURI, localPart); } else { writer.writeStartElement(localPart); } writeComplexProperties(prop, getId(prop, id)); writer.writeEndElement(); }else if (value != null) { writer.writeCharacters(value); } writer.writeEndElement(); }else if(value==null && Utils.isNillable(typeA)){ if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } writer.writeAttribute("http://www.w3.org/2001/XMLSchema-instance", "nil", "1"); writer.writeEndElement(); } } // we add the geometry } else { if (valueA != null) { final boolean descIsType = Utils.isGeometricType(typeA.getName()) && Utils.isGeometricType(nameA); if(!descIsType){ if (namespaceProperty != null && !namespaceProperty.isEmpty()) { writer.writeStartElement(namespaceProperty, nameProperty); } else { writer.writeStartElement(nameProperty); } } final CoordinateReferenceSystem crs = FeatureExt.getCRS(typeA); final JAXBElement element; final MarshallerPool POOL; if ("3.1.1".equals(gmlVersion)) { final Geometry isoGeometry = JTSUtils.toISO((com.vividsolutions.jts.geom.Geometry) valueA, crs); element = OBJECT_FACTORY.buildAnyGeometry(isoGeometry); POOL = GML_31_POOL; } else if ("3.2.1".equals(gmlVersion)) { AbstractGeometry gmlGeometry = null; try { gmlGeometry = JTStoGeometry.toGML(gmlVersion, (com.vividsolutions.jts.geom.Geometry) valueA, crs); } catch (FactoryException ex) { LOGGER.log(Level.WARNING, "Factory exception when transforming JTS geometry to GML binding", ex); } if(gmlGeometry!=null){ //id is requiered in version 3.2.1 //NOTE we often see gml where the geometry id is the same as the feature // we use the last parent with an id, seems acceptable. final String gid = (id+"_g").replace(':', '_'); setId(gmlGeometry, gid); } element = GML32_FACTORY.buildAnyGeometry(gmlGeometry); POOL = GML_32_POOL; } else { throw new IllegalArgumentException("Unexpected GML version:" + gmlVersion); } try { final Marshaller marshaller; marshaller = POOL.acquireMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false); marshaller.marshal(element, writer); POOL.recycle(marshaller); } catch (JAXBException ex) { LOGGER.log(Level.WARNING, "JAXB Exception while marshalling the iso geometry: " + ex.getMessage(), ex); } if(!descIsType)writer.writeEndElement(); } } } /** * * @param gmlGeometry * @param id * @param inc auto increment value, ids must be unique */ private void setId(AbstractGeometry gmlGeometry, String id){ if(gmlGeometry.getId()==null || gmlGeometry.getId().isEmpty()){ //do not override ids if they exist gmlGeometry.setId(id+(gidInc)); gidInc++; } if(gmlGeometry instanceof MultiCurve){ for(CurveProperty po : ((MultiCurve)gmlGeometry).getCurveMember()){ final AbstractCurve child = po.getAbstractCurve(); if(child instanceof AbstractGeometry){ setId((AbstractGeometry) child, id); } } }else if(gmlGeometry instanceof MultiSurface){ for(SurfaceProperty po : ((MultiSurface)gmlGeometry).getSurfaceMember()){ final AbstractSurface child = po.getAbstractSurface(); if(child instanceof AbstractGeometry){ setId((AbstractGeometry) child, id); } } }else if(gmlGeometry instanceof MultiGeometryType){ for(GeometryPropertyType po : ((MultiGeometryType)gmlGeometry).getGeometryMember()){ final AbstractGeometryType child = po.getAbstractGeometry(); if(child instanceof AbstractGeometry){ setId((AbstractGeometry) child, id); } } }else if(gmlGeometry instanceof MultiSolidType){ for(SolidPropertyType po : ((MultiSolidType)gmlGeometry).getSolidMember()){ final AbstractSolidType child = po.getAbstractSolid().getValue(); if(child instanceof AbstractGeometry){ setId((AbstractGeometry) child, id); } } }else if(gmlGeometry instanceof MultiPointType){ for(PointPropertyType po : ((MultiPointType)gmlGeometry).getPointMember()){ final PointType child = po.getPoint(); if(child instanceof AbstractGeometry){ setId((AbstractGeometry) child, id); } } } } /** * * @param featureCollection * @param writer * @param fragment : true if we write in a stream, dont write start and end elements * @throws DataStoreException */ public void writeFeatureCollection(final FeatureCollection featureCollection, final boolean fragment, final Integer nbMatched) throws DataStoreException, XMLStreamException { // the XML header if(!fragment){ writer.writeStartDocument("UTF-8", "1.0"); } // the root Element writer.writeStartElement("wfs", "FeatureCollection", wfsNamespace); // id does not appear in WFS 2 if (!"2.0.0".equals(wfsVersion)) { String collectionID = ""; if (featureCollection.getID() != null) { collectionID = featureCollection.getID(); } writer.writeAttribute("gml", gmlNamespace, "id", collectionID); } // timestamp synchronized(FORMATTER) { writer.writeAttribute("timeStamp", FORMATTER.format(new Date(System.currentTimeMillis()))); } writer.writeNamespace("gml", gmlNamespace); writer.writeNamespace("wfs", wfsNamespace); writer.writeNamespace("xsi", XSI_NAMESPACE); if (schemaLocation != null && !schemaLocation.equals("")) { writer.writeAttribute("xsi", XSI_NAMESPACE, "schemaLocation", schemaLocation); } /* * Other version dependant WFS feature collection attribute */ if ("2.0.0".equals(wfsVersion)) { writer.writeAttribute("numberReturned", Integer.toString(featureCollection.size())); if (nbMatched != null) { writer.writeAttribute("numberMatched", Integer.toString(nbMatched)); } } else { writer.writeAttribute("numberOfFeatures", Integer.toString(featureCollection.size())); } FeatureType type = featureCollection.getFeatureType(); if (type != null && type.getName() != null) { for(String n : Utils.listAllNamespaces(type)){ if (n != null && !(n.equals("http://www.opengis.net/gml") || n.equals("http://www.opengis.net/gml/3.2")) && !n.isEmpty()) { writer.writeNamespace(getPrefix(n).prefix, n); } } } /* * The boundedby part */ writeBounds(featureCollection.getEnvelope(), writer); // we write each feature member of the collection FeatureIterator iterator = featureCollection.iterator(); try { while (iterator.hasNext()) { final Feature f = iterator.next(); if ("2.0.0".equals(wfsVersion)) { writer.writeStartElement("wfs", "member", wfsNamespace); } else { writer.writeStartElement("gml", "featureMember", gmlNamespace); } writeFeature(f, false); writer.writeEndElement(); } } finally { // we close the stream iterator.close(); } writer.writeEndElement(); if(!fragment){ writer.writeEndDocument(); } writer.flush(); if(!fragment){ writer.close(); } } private void writeBounds(final Envelope bounds, final XMLStreamWriter streamWriter) throws XMLStreamException { if (bounds != null) { String srsName = null; if (bounds.getCoordinateReferenceSystem() != null) { try { srsName = IdentifiedObjects.lookupURN(bounds.getCoordinateReferenceSystem(), null); } catch (FactoryException ex) { LOGGER.log(Level.WARNING, null, ex); } } if ("2.0.0".equals(wfsVersion)) { streamWriter.writeStartElement("wfs", "boundedBy", wfsNamespace); } else { streamWriter.writeStartElement("gml", "boundedBy", gmlNamespace); } streamWriter.writeStartElement("gml", "Envelope", gmlNamespace); if (srsName != null) { streamWriter.writeAttribute("srsName", srsName); } else { streamWriter.writeAttribute("srsName", ""); } // lower corner streamWriter.writeStartElement("gml", "lowerCorner", gmlNamespace); String lowValue = bounds.getLowerCorner().getOrdinate(0) + " " + bounds.getLowerCorner().getOrdinate(1); streamWriter.writeCharacters(lowValue); streamWriter.writeEndElement(); // upper corner streamWriter.writeStartElement("gml", "upperCorner", gmlNamespace); String uppValue = bounds.getUpperCorner().getOrdinate(0) + " " + bounds.getUpperCorner().getOrdinate(1); streamWriter.writeCharacters(uppValue); streamWriter.writeEndElement(); streamWriter.writeEndElement(); streamWriter.writeEndElement(); } } /** * * @param name * @return true if property is an atribute, starts by a @ */ public static boolean isAttributeProperty(GenericName name){ final String localPart = name.tip().toString(); return !localPart.isEmpty() && localPart.charAt(0) == '@'; } }