/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009, 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.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import java.util.Collection; import java.util.Date; import java.util.logging.Level; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.sis.feature.FeatureExt; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.internal.jaxb.ObjectFactory; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.util.logging.Logging; import org.apache.sis.xml.Namespaces; import org.apache.sis.xml.MarshallerPool; import org.geotoolkit.feature.xml.Utils; import org.geotoolkit.geometry.isoonjts.JTSUtils; import org.geotoolkit.internal.jaxb.JTSWrapperMarshallerPool; import org.geotoolkit.util.NamesExt; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.feature.PropertyType; import org.opengis.util.GenericName; import org.opengis.geometry.Envelope; import org.opengis.geometry.Geometry; import org.opengis.util.FactoryException; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * * @author Guilhem Legal (Geomatys) */ public class ElementFeatureWriter { /** * Logger for this writer. */ protected static final Logger LOGGER = Logging.getLogger("org.geotoolkit.feature.xml.jaxp"); /** * The pool of marshallers used for marshalling geometries. */ private static final MarshallerPool POOL = JTSWrapperMarshallerPool.getInstance(); /** * Object factory to build a geometry. */ private static final ObjectFactory OBJECT_FACTORY = new ObjectFactory(); /** * GML namespace for this class. */ private static final String GML = "http://www.opengis.net/gml"; protected String schemaLocation; private int lastUnknowPrefix = 0; private final Map<String, String> unknowNamespaces = new HashMap<String, String>(); private static final DateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); public ElementFeatureWriter() { } public ElementFeatureWriter(final Map<String, String> schemaLocations) { 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(' '); } if(sb.length()>0){ sb.setLength(sb.length()-1); //remove last ' ' } schemaLocation = sb.toString(); } } public Element write(final Object candidate, final boolean fragment) throws IOException, DataStoreException, ParserConfigurationException { return write(candidate, fragment, null); } /** * {@inheritDoc} */ public Element write(final Object candidate, final boolean fragment, final Integer nbMatched) throws IOException, DataStoreException, ParserConfigurationException { if (candidate instanceof Feature) { return writeFeature((Feature) candidate, null, fragment); } else if (candidate instanceof FeatureCollection) { return writeFeatureCollection((FeatureCollection) candidate, fragment, true, 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 */ public Element writeFeature(final Feature feature,final Document rootDocument, boolean fragment) throws ParserConfigurationException { final Document document; if (rootDocument == null) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // then we have to create document-loader: factory.setNamespaceAware(false); DocumentBuilder loader = factory.newDocumentBuilder(); // creating a new DOM-document... document = loader.newDocument(); } else { document = rootDocument; } //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 Element rootElement; final Prefix prefix; if (namespace != null) { prefix = getPrefix(namespace); rootElement = document.createElementNS(namespace, localPart); rootElement.setPrefix(prefix.prefix); } else { rootElement = document.createElement(localPart); prefix = null; } // if main document set the xmlns if (!fragment) { rootElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:gml", "http://www.opengis.net/gml"); } final Attr idAttr = document.createAttributeNS(GML, "id"); idAttr.setValue(feature.getPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString()).toString()); idAttr.setPrefix("gml"); rootElement.setAttributeNodeNS(idAttr); if (rootDocument == null) { document.appendChild(rootElement); } //write properties in the type order for(final PropertyType desc : type.getProperties(true)){ if(AttributeConvention.contains(desc.getName())) continue; if (desc.getName().tip().toString().startsWith("@")) { //skip attributes continue; } final Collection values = Utils.propertyValueAsList(feature, desc.getName().toString()); for (Object valueA : values) { final PropertyType typeA = desc; final GenericName nameA = desc.getName(); final String nameProperty = nameA.tip().toString(); String namespaceProperty = NamesExt.getNamespace(nameA); if (valueA instanceof Collection && !(AttributeConvention.isGeometryAttribute(typeA))) { for (Object value : (Collection)valueA) { final Element element; if (namespaceProperty != null) { element = document.createElementNS(namespaceProperty, nameProperty); } else { element = document.createElement(nameProperty); } element.setTextContent(Utils.getStringValue(value)); if (prefix != null) { element.setPrefix(prefix.prefix); } rootElement.appendChild(element); } } else if (valueA != null && valueA.getClass().isArray() && !(AttributeConvention.isGeometryAttribute(typeA))) { final int length = Array.getLength(valueA); for (int i = 0; i < length; i++){ final Element element; if (namespaceProperty != null) { element = document.createElementNS(namespaceProperty, nameProperty); } else { element = document.createElement(nameProperty); } 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); } element.setTextContent(textValue); if (prefix != null) { element.setPrefix(prefix.prefix); } rootElement.appendChild(element); } } else if (valueA instanceof Map && !(AttributeConvention.isGeometryAttribute(typeA))) { final Map<?,?> map = (Map)valueA; for (Entry<?,?> entry : map.entrySet()) { final Element element; if (namespaceProperty != null) { element = document.createElementNS(namespaceProperty, nameProperty); } else { element = document.createElement(nameProperty); } final Object key = entry.getKey(); if (key != null) { element.setAttribute("name", (String)key); } element.setTextContent(Utils.getStringValue(entry.getValue())); if (prefix != null) { element.setPrefix(prefix.prefix); } rootElement.appendChild(element); } } else if (!(AttributeConvention.isGeometryAttribute(typeA))) { String value = Utils.getStringValue(valueA); if (value != null || (value == null && !Utils.isNillable(typeA))) { if ((nameProperty.equals("name") || nameProperty.equals("description")) && !GML.equals(namespaceProperty)) { namespaceProperty = GML; LOGGER.finer("the property name and description of a feature must have the GML namespace"); } final Element element; if (namespaceProperty != null) { element = document.createElementNS(namespaceProperty, nameProperty); } else { element = document.createElement(nameProperty); } if (value != null) { element.setTextContent(value); } if (prefix != null) { element.setPrefix(prefix.prefix); } rootElement.appendChild(element); } // we add the geometry } else { if (valueA != null) { final Element element; if (namespaceProperty != null) { element = document.createElementNS(namespaceProperty, nameProperty); } else { element = document.createElement(nameProperty); } if (prefix != null) { element.setPrefix(prefix.prefix); } Geometry isoGeometry = JTSUtils.toISO((com.vividsolutions.jts.geom.Geometry) valueA, FeatureExt.getCRS(type)); try { final Marshaller marshaller; marshaller = POOL.acquireMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false); marshaller.marshal(OBJECT_FACTORY.buildAnyGeometry(isoGeometry), element); POOL.recycle(marshaller); } catch (JAXBException ex) { LOGGER.log(Level.WARNING, "JAXB Exception while marshalling the iso geometry: " + ex.getMessage(), ex); } rootElement.appendChild(element); } } } } //writer.writeEndElement(); return rootElement; } /** * * @param featureCollection * @param writer * @param fragment : true if we write in a stream, dont write start and end elements * @throws DataStoreException */ public Element writeFeatureCollection(final FeatureCollection featureCollection, final boolean fragment, final boolean wfs, final Integer nbMatched) throws DataStoreException, ParserConfigurationException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // then we have to create document-loader: factory.setNamespaceAware(false); DocumentBuilder loader = factory.newDocumentBuilder(); // creating a new DOM-document... Document document = loader.newDocument(); // the XML header if (!fragment) { document.setXmlVersion("1.0"); //writer.writeStartDocument("UTF-8", "1.0"); } // the root Element final Element rootElement; if (wfs) { rootElement = document.createElementNS("http://www.opengis.net/wfs", "FeatureCollection"); rootElement.setPrefix("wfs"); } else { rootElement = document.createElementNS("http://www.opengis.net/gml", "FeatureCollection"); rootElement.setPrefix("gml"); } document.appendChild(rootElement); String collectionID = ""; if (featureCollection.getID() != null) { collectionID = featureCollection.getID(); } final Attr idAttribute = document.createAttributeNS(GML, "id"); idAttribute.setValue(collectionID); idAttribute.setPrefix("gml"); rootElement.setAttributeNodeNS(idAttribute); rootElement.setAttribute("numberOfFeatures", Integer.toString(featureCollection.size())); if (nbMatched != null) { rootElement.setAttribute("numberMatched", Integer.toString(nbMatched)); } // timestamp synchronized(FORMATTER) { rootElement.setAttribute("timeStamp", FORMATTER.format(new Date(System.currentTimeMillis()))); } if (schemaLocation != null && !schemaLocation.equals("")) { rootElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", schemaLocation); } /*FeatureType type = featureCollection.getFeatureType(); if (type != null && type.getName() != null) { String namespace = type.getName().getNamespaceURI(); if (namespace != null && !namespace.equals(GML)) { Prefix prefix = getPrefix(namespace); writer.writeNamespace(prefix.prefix, namespace); } }*/ /* * The boundedby part */ final Element boundElement = writeBounds(featureCollection.getEnvelope(), document); if (boundElement != null) { rootElement.appendChild(boundElement); } // we write each feature member of the collection FeatureIterator iterator = featureCollection.iterator(); try { while (iterator.hasNext()) { final Feature f = iterator.next(); final Element memberElement = document.createElementNS(GML, "featureMember"); memberElement.setPrefix("gml"); memberElement.appendChild(writeFeature(f, document, true)); rootElement.appendChild(memberElement); } } finally { // we close the stream iterator.close(); } return rootElement; } private Element writeBounds(final Envelope bounds, final Document document) { 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); } } final Element boundElement = document.createElementNS(GML, "boundedBy"); boundElement.setPrefix("gml"); final Element envElement = document.createElementNS(GML, "Envelope"); envElement.setPrefix("gml"); if (srsName != null) { envElement.setAttribute("srsName", srsName); } else { envElement.setAttribute("srsName", ""); } // lower corner final Element lower = document.createElementNS(GML, "lowerCorner"); String lowValue = bounds.getLowerCorner().getOrdinate(0) + " " + bounds.getLowerCorner().getOrdinate(1); lower.setTextContent(lowValue); lower.setPrefix("gml"); envElement.appendChild(lower); // upper corner final Element upper = document.createElementNS(GML, "upperCorner"); String uppValue = bounds.getUpperCorner().getOrdinate(0) + " " + bounds.getUpperCorner().getOrdinate(1); upper.setTextContent(uppValue); upper.setPrefix("gml"); envElement.appendChild(upper); boundElement.appendChild(envElement); return boundElement; } return null; } /** * Returns the prefix for the given namespace. * * @param namespace The namespace for which we want the prefix. */ private Prefix getPrefix(final String namespace) { String prefix = Namespaces.getPreferredPrefix(namespace, null); boolean unknow = false; if (prefix == null) { prefix = unknowNamespaces.get(namespace); if (prefix == null) { prefix = "ns" + lastUnknowPrefix; lastUnknowPrefix++; unknow = true; unknowNamespaces.put(namespace, prefix); } } return new Prefix(unknow, prefix); } /** * Inner class for handling prefix and if it is already known. */ private final class Prefix { public boolean unknow; public String prefix; public Prefix(final boolean unknow, final String prefix) { this.prefix = prefix; this.unknow = unknow; } } }