/*
* 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.data.wfs.xml;
import com.vividsolutions.jts.geom.Geometry;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.geotoolkit.feature.xml.Utils;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.wfs.Delete;
import org.geotoolkit.data.wfs.IdentifierGenerationOption;
import org.geotoolkit.data.wfs.Insert;
import org.geotoolkit.data.wfs.Native;
import org.geotoolkit.data.wfs.ReleaseAction;
import org.geotoolkit.data.wfs.TransactionElement;
import org.geotoolkit.data.wfs.TransactionRequest;
import org.geotoolkit.data.wfs.Update;
import org.geotoolkit.feature.xml.jaxp.JAXPStreamFeatureWriter;
import org.geotoolkit.gml.JTStoGeometry;
import org.geotoolkit.gml.xml.GMLMarshallerPool;
import org.geotoolkit.gml.xml.v311.AbstractGeometryType;
import org.geotoolkit.gml.xml.v311.GeometryPropertyType;
import org.geotoolkit.internal.jaxb.JTSWrapperMarshallerPool;
import org.geotoolkit.internal.jaxb.ObjectFactory;
import org.geotoolkit.metadata.Citations;
import org.geotoolkit.referencing.IdentifiedObjects;
import org.geotoolkit.sld.xml.StyleXmlIO;
import org.apache.sis.xml.MarshallerPool;
import org.geotoolkit.util.NamesExt;
import org.opengis.feature.PropertyType;
import org.opengis.util.GenericName;
import org.opengis.filter.Filter;
import org.opengis.util.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class JAXPStreamTransactionWriter {
public static final String GML_NAMESPACE = "http://www.opengis.net/gml";
public static final String GML_PREFIX = "gml";
public static final String OGC_NAMESPACE = "http://www.opengis.net/ogc";
public static final String OGC_PREFIX = "ogc";
public static final String WFS_NAMESPACE = "http://www.opengis.net/wfs";
public static final String WFS_PREFIX = "wfs";
public static final String XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance";
public static final String XSI_PREFIX = "xsi";
public static final String XS_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
public static final String XS_PREFIX = "xs";
private static final String TAG_TRANSACTION = "Transaction";
private static final String TAG_INSERT = "Insert";
private static final String TAG_UPDATE = "Update";
private static final String TAG_DELETE = "Delete";
private static final String TAG_NATIVE = "Native";
private static final String TAG_LOCKID = "LockId";
private static final String TAG_PROPERTY = "Property";
private static final String TAG_NAME = "Name";
private static final String TAG_VALUE = "Value";
private static final String TAG_FILTER = "Filter";
private static final String PROP_SERVICE = "service";
private static final String PROP_VERSION = "version";
private static final String PROP_RELEASEACTION = "releaseAction";
private static final String PROP_VENDORID = "vendorId";
private static final String PROP_SAFETOIGNORE = "safeToIgnore";
private static final String PROP_HANDLE = "handle";
private static final String PROP_TYPENAME = "typeName";
private static final String PROP_SRSNAME = "srsName";
private static final String PROP_INPUTFORMAT = "inputFormat";
private static final String PROP_IDGEN = "idgen";
private static final String PROP_TYPE = "type";
private static final String TYPE_STRING = XS_PREFIX+":string";
private static final String TYPE_DECIMAL = XS_PREFIX+":decimal";
private static final String TYPE_INTEGER = XS_PREFIX+":int";
private static final String TYPE_BOOLEAN = XS_PREFIX+":boolean";
private static final String TYPE_DATE = XS_PREFIX+":date";
private final AtomicInteger inc = new AtomicInteger();
private static final MarshallerPool POOL = JTSWrapperMarshallerPool.getInstance();
private static final MarshallerPool GMLPOOL = GMLMarshallerPool.getInstance();
public void write(final OutputStream out, final TransactionRequest request)
throws XMLStreamException, FactoryException, JAXBException, DataStoreException, IOException{
final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
final XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(out);
// the XML header
streamWriter.writeStartDocument("UTF-8", "1.0");
//set the namespaces
streamWriter.setDefaultNamespace(WFS_NAMESPACE);
streamWriter.setPrefix(GML_PREFIX, GML_NAMESPACE);
streamWriter.setPrefix(OGC_PREFIX, OGC_NAMESPACE);
streamWriter.setPrefix(WFS_PREFIX, WFS_NAMESPACE);
streamWriter.setPrefix(XSI_PREFIX, XSI_NAMESPACE);
streamWriter.setPrefix(XS_PREFIX, XS_NAMESPACE);
//write the request
write(streamWriter, request);
streamWriter.writeEndDocument();
streamWriter.flush();
streamWriter.close();
}
private void write(final XMLStreamWriter writer, final TransactionRequest request)
throws XMLStreamException, FactoryException, JAXBException, DataStoreException, IOException{
writer.writeStartElement(WFS_PREFIX, TAG_TRANSACTION, WFS_NAMESPACE);
writer.writeAttribute(PROP_SERVICE, "WFS");
writer.writeAttribute(PROP_VERSION, "1.1.0");
//write the namespaces
writer.writeAttribute("xmlns:"+GML_PREFIX, GML_NAMESPACE);
writer.writeAttribute("xmlns:"+OGC_PREFIX, OGC_NAMESPACE);
writer.writeAttribute("xmlns:"+WFS_PREFIX, WFS_NAMESPACE);
writer.writeAttribute("xmlns:"+XSI_PREFIX, XSI_NAMESPACE);
writer.writeAttribute("xmlns:"+XS_PREFIX, XS_NAMESPACE);
//write action if there is one------------------------------------------
final ReleaseAction action = request.getReleaseAction();
if(action != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_RELEASEACTION, action.name());
}
//write lock if there is one--------------------------------------------
final String lockId = request.getLockId();
if(lockId != null){
writer.writeStartElement(WFS_PREFIX, TAG_LOCKID, WFS_NAMESPACE);
writer.writeCharacters(lockId);
writer.writeEndElement();
}
//write the transaction elements----------------------------------------
for(TransactionElement t : request.elements()){
if(t instanceof Insert){
write(writer,(Insert)t);
}else if(t instanceof Update){
write(writer,(Update)t);
}else if(t instanceof Delete){
write(writer,(Delete)t);
}else if(t instanceof Native){
write(writer,(Native)t);
}
}
writer.writeEndElement();
}
// <xsd:element name="Insert" type="wfs:InsertElementType"/>
// <xsd:complexType name="InsertElementType">
// <xsd:choice>
// <xsd:element ref="gml:_FeatureCollection" />
// <xsd:sequence>
// <xsd:element ref="gml:_Feature" maxOccurs="unbounded"/>
// </xsd:sequence>
// </xsd:choice>
// <xsd:attribute name="idgen"
// type="wfs:IdentifierGenerationOptionType"
// use="optional" default="GenerateNew"/>
// <xsd:attribute name="handle" type="xsd:string" use="optional"/>
// <xsd:attribute name="inputFormat" type="xsd:string"
// use="optional" default="text/xml; subversion=gml/3.1.1"/>
// <xsd:attribute name="srsName" type="xsd:anyURI" use="optional"/>
// </xsd:complexType>
// <xsd:simpleType name="IdentifierGenerationOptionType">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="UseExisting"/>
// <xsd:enumeration value="ReplaceDuplicate"/>
// <xsd:enumeration value="GenerateNew"/>
// </xsd:restriction>
// </xsd:simpleType>
private void write(final XMLStreamWriter writer, final Insert element)
throws XMLStreamException, FactoryException, JAXBException, DataStoreException, IOException{
writer.writeStartElement(WFS_PREFIX, TAG_INSERT, WFS_NAMESPACE);
//write id gen----------------------------------------------------------
final IdentifierGenerationOption opt = element.getIdentifierGenerationOption();
if(opt != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_IDGEN, opt.value());
}
//write handle----------------------------------------------------------
final String handle = element.getHandle();
if(handle != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_HANDLE, handle);
}
//write format----------------------------------------------------------
final String format = element.getInputFormat();
if(format != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_INPUTFORMAT, format);
}
//write crs-------------------------------------------------------------
final CoordinateReferenceSystem crs = element.getCoordinateReferenceSystem();
if(crs != null){
final String id = IdentifiedObjects.lookupIdentifier(Citations.URN_OGC, crs, true);
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_SRSNAME, id);
}
//write features--------------------------------------------------------
final FeatureCollection col = element.getFeatures();
final JAXPStreamFeatureWriter fw = new JAXPStreamFeatureWriter();
fw.setOutput(writer);
fw.writeFeatureCollection(col,true, null);
writer.writeEndElement();
}
// <xsd:element name="Update" type="wfs:UpdateElementType"/>
// <xsd:complexType name="UpdateElementType">
// <xsd:sequence>
// <xsd:element ref="wfs:Property" maxOccurs="unbounded"/>
// <xsd:element ref="ogc:Filter" minOccurs="0" maxOccurs="1"/>
// </xsd:sequence>
// <xsd:attribute name="handle" type="xsd:string" use="optional"/>
// <xsd:attribute name="typeName" type="xsd:QName" use="required"/>
// <xsd:attribute name="inputFormat" type="xsd:string"
// use="optional" default="text/xml; subversion=gml/3.1.1"/>
// <xsd:attribute name="srsName" type="xsd:anyURI" use="optional"/>
// </xsd:complexType>
// <xsd:element name="Property" type="wfs:PropertyType"/>
// <xsd:complexType name="PropertyType">
// <xsd:sequence>
// <xsd:element name="Name" type="xsd:QName"/>
// <xsd:element name="Value" minOccurs="0"/>
// </xsd:sequence>
// </xsd:complexType>
private void write(final XMLStreamWriter writer, final Update element)
throws XMLStreamException, FactoryException, JAXBException{
writer.writeStartElement(WFS_PREFIX, TAG_UPDATE, WFS_NAMESPACE);
//write typename--------------------------------------------------------
final GenericName typeName = element.getTypeName();
final String ns = NamesExt.getNamespace(typeName);
if (ns != null && !ns.isEmpty()) {
final String prefix = "geons"+inc.incrementAndGet();
writer.writeAttribute("xmlns:"+prefix, ns);
writer.writeAttribute(PROP_TYPENAME, prefix+":"+typeName.tip().toString());
} else {
writer.writeAttribute(PROP_TYPENAME, typeName.tip().toString());
}
//write crs-------------------------------------------------------------
final CoordinateReferenceSystem crs = element.getCoordinateReferenceSystem();
if(crs != null){
final String id = IdentifiedObjects.lookupIdentifier(Citations.URN_OGC, crs, true);
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_SRSNAME, id);
}
//write format----------------------------------------------------------
final String format = element.getInputFormat();
if(format != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_INPUTFORMAT, format);
}
//write handle----------------------------------------------------------
final String handle = element.getHandle();
if(handle != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_HANDLE, handle);
}
//write filter ---------------------------------------------------------
final Filter filter = element.getFilter();
if(filter != null){
final StyleXmlIO util = new StyleXmlIO();
final Marshaller marshaller = StyleXmlIO.getJaxbContext110().acquireMarshaller();
marshaller.setProperty(marshaller.JAXB_FRAGMENT, Boolean.TRUE);
final Object jaxbelement = util.getTransformerXMLv110().visit(filter);
marshaller.marshal(jaxbelement, writer);
StyleXmlIO.getJaxbContext110().recycle(marshaller);
writer.flush();
}
//write properties------------------------------------------------------
for(final Entry<PropertyType,Object> entry : element.updates().entrySet()){
writer.writeStartElement(WFS_PREFIX, TAG_PROPERTY, WFS_NAMESPACE);
//write namespace
final GenericName name = entry.getKey().getName();
final String ns2 = NamesExt.getNamespace(name);
String pref = writer.getNamespaceContext().getPrefix(ns2);
if(pref == null && ns2 != null && !ns2.isEmpty()){
pref = "geons"+inc.incrementAndGet();
writer.writeAttribute("xmlns:"+pref, ns2);
}
//write name
writer.writeStartElement(WFS_PREFIX, TAG_NAME, WFS_NAMESPACE);
if (pref != null) {
writer.writeCharacters(pref+":"+name.tip().toString());
} else {
writer.writeCharacters(name.tip().toString());
}
writer.writeEndElement();
//write value
final PropertyType propertyType = entry.getKey();
Object value = entry.getValue();
if(value != null){
//todo must handle geometry differently
if(value instanceof Geometry){
value = new GeometryPropertyType((AbstractGeometryType)JTStoGeometry.toGML("3.1.1", (Geometry)value));
final Marshaller marshaller = GMLPOOL.acquireMarshaller();
marshaller.setProperty(marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(new ObjectFactory().createValue(value), writer);
GMLPOOL.recycle(marshaller);
}else if(value instanceof org.opengis.geometry.Geometry){
final Marshaller marshaller = POOL.acquireMarshaller();
marshaller.setProperty(marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(new ObjectFactory().createValue(value), writer);
POOL.recycle(marshaller);
}else{
writer.writeStartElement(WFS_PREFIX, TAG_VALUE, WFS_NAMESPACE);
QName qname = Utils.getQNameFromType(propertyType,"");
writer.writeAttribute(XSI_PREFIX, XSI_NAMESPACE, PROP_TYPE, qname.getLocalPart());
writer.writeCharacters(Utils.getStringValue(value));
writer.writeEndElement();
}
}
writer.writeEndElement();
}
writer.writeEndElement();
}
// <xsd:element name="Delete" type="wfs:DeleteElementType"/>
// <xsd:complexType name="DeleteElementType">
// <xsd:sequence>
// <xsd:element ref="ogc:Filter" minOccurs="1" maxOccurs="1"/>
// </xsd:sequence>
// <xsd:attribute name="handle" type="xsd:string" use="optional"/>
// <xsd:attribute name="typeName" type="xsd:QName" use="required"/>
// </xsd:complexType>
private void write(final XMLStreamWriter writer, final Delete element) throws XMLStreamException, JAXBException{
writer.writeStartElement(WFS_PREFIX, TAG_DELETE, WFS_NAMESPACE);
//write typename--------------------------------------------------------
final GenericName typeName = element.getTypeName();
final String ns = NamesExt.getNamespace(typeName);
if (ns != null && !ns.isEmpty()) {
final String prefix = "geons"+inc.incrementAndGet();
writer.writeAttribute("xmlns:"+prefix, ns);
writer.writeAttribute(PROP_TYPENAME, prefix+":"+typeName.tip().toString());
} else {
writer.writeAttribute(PROP_TYPENAME, typeName.tip().toString());
}
//write handle----------------------------------------------------------
final String handle = element.getHandle();
if(handle != null){
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_HANDLE, handle);
}
//write filter ---------------------------------------------------------
final StyleXmlIO util = new StyleXmlIO();
final Marshaller marshaller = util.getJaxbContext110().acquireMarshaller();
marshaller.setProperty(marshaller.JAXB_FRAGMENT, Boolean.TRUE);
final Object jaxbelement = util.getTransformerXMLv110().visit(element.getFilter());
marshaller.marshal(jaxbelement, writer);
util.getJaxbContext110().recycle(marshaller);
writer.writeEndElement();
}
// <xsd:element name="Native" type="wfs:NativeType"/>
// <xsd:complexType name="NativeType">
// <xsd:attribute name="vendorId" type="xsd:string" use="required"/>
// <xsd:attribute name="safeToIgnore" type="xsd:boolean" use="required"/>
// </xsd:complexType>
private void write(final XMLStreamWriter writer, final Native element) throws XMLStreamException{
writer.writeStartElement(WFS_PREFIX, TAG_NATIVE, WFS_NAMESPACE);
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_VENDORID, element.getVendorId());
writer.writeAttribute(WFS_PREFIX, WFS_NAMESPACE, PROP_SAFETOIGNORE, Boolean.valueOf(element.isSafeToIgnore()).toString());
writer.writeEndElement();
}
private static String bestType(final Object candidate){
if(candidate instanceof String){
return TYPE_STRING;
}else if(candidate instanceof Integer){
return TYPE_INTEGER;
}else if(candidate instanceof Boolean){
return TYPE_BOOLEAN;
}else if(candidate instanceof Date){
return TYPE_DATE;
}else if(candidate instanceof Float || candidate instanceof Double){
return TYPE_DECIMAL;
}else if(candidate instanceof org.opengis.geometry.Geometry){
//is that correct ?
return GML_PREFIX+':'+"Geometry";
}else{
throw new IllegalArgumentException("Unexpected attribut type : "+ candidate.getClass());
}
}
}