/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2016, 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.jaxb; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.namespace.QName; 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.feature.Features; import org.apache.sis.internal.feature.AttributeConvention; import org.geotoolkit.xml.AbstractConfigurable; import org.apache.sis.xml.MarshallerPool; import org.geotoolkit.feature.xml.GMLConvention; import org.geotoolkit.feature.xml.Utils; import org.geotoolkit.util.NamesExt; import org.geotoolkit.xsd.xml.v2001.Attribute; import org.geotoolkit.xsd.xml.v2001.ComplexContent; import org.geotoolkit.xsd.xml.v2001.Element; import org.geotoolkit.xsd.xml.v2001.ExplicitGroup; import org.geotoolkit.xsd.xml.v2001.ExtensionType; import org.geotoolkit.xsd.xml.v2001.FormChoice; import org.geotoolkit.xsd.xml.v2001.Import; import org.geotoolkit.xsd.xml.v2001.LocalElement; import org.geotoolkit.xsd.xml.v2001.Schema; import org.geotoolkit.xsd.xml.v2001.TopLevelComplexType; import org.geotoolkit.xsd.xml.v2001.TopLevelElement; import org.geotoolkit.xsd.xml.v2001.XSDMarshallerPool; import org.opengis.feature.AttributeType; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.FeatureType; import org.opengis.feature.Operation; import org.opengis.feature.PropertyType; import org.opengis.util.GenericName; import org.w3c.dom.Document; import org.w3c.dom.Node; /** * * @author Guilhem Legal (Geomatys) * @author Johann Sorel (Geomatys) */ public class JAXBFeatureTypeWriter extends AbstractConfigurable { private static final MarshallerPool POOL = XSDMarshallerPool.getInstance(); private static final Import GML_IMPORT_311 = new Import("http://www.opengis.net/gml", "http://schemas.opengis.net/gml/3.1.1/base/gml.xsd"); private static final Import GML_IMPORT_321 = new Import("http://www.opengis.net/gml/3.2", "http://schemas.opengis.net/gml/3.2.1/gml.xsd"); private static final QName ABSTRACT_FEATURE_NAME_311 = new QName("http://www.opengis.net/gml", "_Feature"); private static final QName ABSTRACT_FEATURE_TYPE_311 = new QName("http://www.opengis.net/gml", "AbstractFeatureType"); private static final QName ABSTRACT_FEATURE_NAME_321 = new QName("http://www.opengis.net/gml/3.2", "AbstractFeature"); private static final QName ABSTRACT_FEATURE_TYPE_321 = new QName("http://www.opengis.net/gml/3.2", "AbstractFeatureType"); private final String gmlVersion; public JAXBFeatureTypeWriter() { this("3.1.1"); } public JAXBFeatureTypeWriter(String gmlVersion) { this.gmlVersion = gmlVersion; } /** * Return an XML representation of the specified featureType. * * @param feature The featureType to marshall. * @return An XML string representing the featureType. */ public String write(FeatureType feature) throws JAXBException { final StringWriter sw = new StringWriter(); write(feature,sw); return sw.toString(); } /** * Write an XML representation of the specified featureType into the Writer. * * @param feature The featureType to marshall. */ public void write(FeatureType feature, Writer writer) throws JAXBException { final Schema schema = getSchemaFromFeatureType(feature); final Marshaller marshaller = POOL.acquireMarshaller(); marshaller.marshal(schema, writer); POOL.recycle(marshaller); } /** * Write an XML representation of the specified featureType into the Stream. * * @param feature The featureType to marshall. */ public void write(FeatureType feature, OutputStream stream) throws JAXBException { final Schema schema = getSchemaFromFeatureType(feature); final Marshaller marshaller = POOL.acquireMarshaller(); marshaller.marshal(schema, stream); POOL.recycle(marshaller); } /** * Write an XML representation of the specified featureType into an Element. * @param feature * @return the xml element. * @throws JAXBException * @throws ParserConfigurationException */ public Node writeToElement(FeatureType feature) throws JAXBException, ParserConfigurationException { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // then we have to create document-loader: factory.setNamespaceAware(false); final DocumentBuilder loader = factory.newDocumentBuilder(); // creating a new DOM-document... final Document document = loader.newDocument(); final Schema schema = getSchemaFromFeatureType(feature); final Marshaller marshaller = POOL.acquireMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false); marshaller.marshal(schema, document); POOL.recycle(marshaller); return document.getDocumentElement(); } /** * Create an xsd schema from a list of feature type. * * @param featureTypes * @return */ public Schema getSchemaFromFeatureType(List<FeatureType> featureTypes) { final Schema schema = new Schema(FormChoice.QUALIFIED, null); if (featureTypes != null && featureTypes.size() > 0) { // we get the first namespace String typeNamespace = null; int i = 0; while (typeNamespace == null && i < featureTypes.size()) { typeNamespace = NamesExt.getNamespace(featureTypes.get(i).getName()); i++; } schema.setTargetNamespace(typeNamespace); if ("3.2.1".equals(gmlVersion)) { schema.addImport(GML_IMPORT_321); } else { schema.addImport(GML_IMPORT_311); } final Set<String> alreadyWritten = new HashSet<>(); for (FeatureType ftype : featureTypes) { fillSchemaWithFeatureType(ftype, schema, true, alreadyWritten); } } return schema; } /** * Create a xsd schema from a feature type. * * @param featureType * @return */ public Schema getSchemaFromFeatureType(FeatureType featureType) { if (featureType != null) { final String typeNamespace = NamesExt.getNamespace(featureType.getName()); final Schema schema = new Schema(FormChoice.QUALIFIED, typeNamespace); if ("3.2.1".equals(gmlVersion)) { schema.addImport(GML_IMPORT_321); } else { schema.addImport(GML_IMPORT_311); } fillSchemaWithFeatureType(featureType, schema, true, new HashSet<String>()); return schema; } return null; } public Schema getExternalSchemaFromFeatureType(String namespace, List<FeatureType> featureTypes) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } private void fillSchemaWithFeatureType(final FeatureType featureType, final Schema schema, boolean addTopElement, Set<String> alreadyWritten) { if (Utils.GML_FEATURE_TYPES.contains(featureType.getName())) { //this type is part of the standard GML types return; } //write parent types for (FeatureType parent : featureType.getSuperTypes()) { fillSchemaWithFeatureType(parent, schema, false, alreadyWritten); } final String typeNamespace = NamesExt.getNamespace(featureType.getName()); final String elementName = featureType.getName().tip().toString(); final String typeName = elementName + "Type"; if (addTopElement) { final TopLevelElement topElement; if ("3.2.1".equals(gmlVersion)) { topElement = new TopLevelElement(elementName, new QName(typeNamespace, typeName), ABSTRACT_FEATURE_NAME_321); } else { topElement = new TopLevelElement(elementName, new QName(typeNamespace, typeName), ABSTRACT_FEATURE_NAME_311); } schema.addElement(topElement); } boolean ar = alreadyWritten.add(typeName); final ExplicitGroup sequence = new ExplicitGroup(); final List<Attribute> attributes = new ArrayList<>(); for (final PropertyType pdesc : featureType.getProperties(false)) { if (AttributeConvention.contains(pdesc.getName())) { //skip convention properties continue; } writeProperty(pdesc, sequence, schema, attributes, alreadyWritten); } if (addTopElement && ar) { final ComplexContent content = getComplexContent(sequence); final TopLevelComplexType tlcType = new TopLevelComplexType(typeName, content); tlcType.getAttributeOrAttributeGroup().addAll(attributes); schema.addComplexType(1, tlcType); } } private void writeComplexType(final FeatureType ctype, final Schema schema, Set<String> alreadyWritten) { final GenericName ptypeName = ctype.getName(); // PropertyType final String nameWithSuffix = Utils.getNameWithTypeSuffix(ptypeName.tip().toString()); boolean write = schema.getTargetNamespace().equals(NamesExt.getNamespace(ptypeName)); //search if this type has already been written if (alreadyWritten.contains(nameWithSuffix)) { return; } alreadyWritten.add(nameWithSuffix); //complex type final ExplicitGroup sequence = new ExplicitGroup(); final TopLevelComplexType tlcType = new TopLevelComplexType(nameWithSuffix, sequence); if (write) { schema.addComplexType(tlcType); } final List<Attribute> attributes = new ArrayList<>(); for (final PropertyType pdesc : ctype.getProperties(true)) { writeProperty(pdesc, sequence, schema, attributes, alreadyWritten); } tlcType.getAttributeOrAttributeGroup().addAll(attributes); } private void writeProperty(final PropertyType pType, final ExplicitGroup sequence, final Schema schema, final List<Attribute> attributes, final Set<String> alreadyWritten) { if(pType instanceof Operation){ //operation types are not written in the xsd. return; } if(pType instanceof AttributeType){ final AttributeType attType = (AttributeType) pType; final String name = attType.getName().tip().toString(); final QName type = Utils.getQNameFromType(attType, gmlVersion); final int minOccurs = attType.getMinimumOccurs(); final int maxOccurs = attType.getMaximumOccurs(); final boolean nillable = FeatureExt.getCharacteristicValue(attType, GMLConvention.NILLABLE_PROPERTY.toString(), minOccurs==0); final String maxOcc; if (maxOccurs == Integer.MAX_VALUE) { maxOcc = "unbounded"; } else { maxOcc = Integer.toString(maxOccurs); } if (name.startsWith("@")) { Attribute att = new Attribute(); att.setName(name.substring(1)); att.setType(type); if (minOccurs == 0) { att.setUse("optional"); } else { att.setUse("required"); } attributes.add(att); } else { sequence.addElement(new LocalElement(name, type, minOccurs, maxOcc, nillable)); } } else if (pType instanceof FeatureAssociationRole) { // for a complexType we have to add 2 complexType (PropertyType and type) final FeatureAssociationRole role = (FeatureAssociationRole) pType; final FeatureType valueType = role.getValueType(); final String name = role.getName().tip().toString(); final QName type = Utils.getQNameFromType(role, gmlVersion); final String typeName = Utils.getNameWithoutTypeSuffix(valueType.getName().tip().toString()); final String propertyName = Utils.getNameWithPropertyTypeSuffix(typeName); final QName proptype; if ("3.2.1".equals(gmlVersion)) { proptype = new QName(GMLConvention.GML_321_NAMESPACE, propertyName); } else { proptype = new QName(GMLConvention.GML_311_NAMESPACE, propertyName); } //property type //<xsd:element name="Address" type="gml:AddressType" xmlns:gml="http://www.opengis.net/gml" nillable="false" minOccurs="1" maxOccurs="1" /> final ExplicitGroup exp = new ExplicitGroup(); final TopLevelComplexType tlcType = new TopLevelComplexType(propertyName, exp); final LocalElement le = new LocalElement(typeName, type, 1, "1",Boolean.FALSE); le.setType(Utils.getQNameFromType(valueType, gmlVersion)); exp.addElement(le); schema.addComplexType(tlcType); //attribute type final int minOccurs = role.getMinimumOccurs(); final int maxOccurs = role.getMaximumOccurs(); final boolean nillable = FeatureExt.getCharacteristicValue(role, GMLConvention.NILLABLE_PROPERTY.toString(), minOccurs==0); final String maxOcc; if (maxOccurs == Integer.MAX_VALUE) { maxOcc = "unbounded"; } else { maxOcc = Integer.toString(maxOccurs); } sequence.addElement(new LocalElement(name, proptype, minOccurs, maxOcc, nillable)); //real type writeComplexType(role.getValueType(), schema, alreadyWritten); } } private ComplexContent getComplexContent(final ExplicitGroup sequence) { final ExtensionType extension; if ("3.2.1".equals(gmlVersion)) { extension = new ExtensionType(ABSTRACT_FEATURE_TYPE_321, sequence); } else { extension = new ExtensionType(ABSTRACT_FEATURE_TYPE_311, sequence); } return new ComplexContent(extension); } }