/*
* 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.jaxb;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.FeatureTypeExt;
import org.geotoolkit.feature.xml.Utils;
import org.geotoolkit.xml.AbstractConfigurable;
import org.apache.sis.xml.MarshallerPool;
import org.apache.sis.xml.Namespaces;
import org.geotoolkit.feature.xml.GMLConvention;
import org.geotoolkit.xsd.xml.v2001.ComplexContent;
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.geotoolkit.util.NamesExt;
import org.opengis.util.GenericName;
import org.geotoolkit.xsd.xml.v2001.Attribute;
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.w3c.dom.Document;
import org.w3c.dom.Node;
/**
*
* @author Guilhem Legal (Geomatys)
* @author Johann Sorel (Geomatys)
*/
public class JAXBFeatureTypeWriterOld 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 int lastUnknowPrefix = 0;
private final Map<String, String> unknowNamespaces = new HashMap<>();
private final String gmlVersion;
public JAXBFeatureTypeWriterOld(){
gmlVersion = "3.1.1";
}
public JAXBFeatureTypeWriterOld(final String gmlVersion){
this.gmlVersion = gmlVersion;
}
public String write(final FeatureType feature) throws JAXBException {
final StringWriter sw = new StringWriter();
write(feature,sw);
return sw.toString();
}
public void write(final FeatureType feature, final Writer writer) throws JAXBException {
final Schema schema = getSchemaFromFeatureType(feature);
final Marshaller marshaller = POOL.acquireMarshaller();
marshaller.marshal(schema, writer);
POOL.recycle(marshaller);
}
public void write(final FeatureType feature, final OutputStream stream) throws JAXBException {
final Schema schema = getSchemaFromFeatureType(feature);
final Marshaller marshaller = POOL.acquireMarshaller();
marshaller.marshal(schema, stream);
POOL.recycle(marshaller);
}
public Node writeToElement(final 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();
}
public Schema getSchemaFromFeatureType(final 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;
}
public Schema getSchemaFromFeatureType(final 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(final String namespace, final List<FeatureType> featureTypes) {
if (featureTypes != null && featureTypes.size() > 0) {
final Schema schema = new Schema(FormChoice.QUALIFIED, namespace);
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, false, alreadyWritten);
}
return schema;
}
return null;
}
private void fillSchemaWithFeatureType(final FeatureType featureType, final Schema schema, boolean addTopElement, Set<String> 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(true)) {
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)
writeComplexType((FeatureType)pType, 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);
}
/**
* Returns the prefix for the given namespace.
*
* @param namespace The namespace for which we want the prefix.
*/
private JAXBFeatureTypeWriterOld.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 JAXBFeatureTypeWriterOld.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;
}
}
}