package org.uncertweb.ps.handler.soap;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.joda.time.LocalDate;
import org.uncertweb.ps.data.DataDescription;
import org.uncertweb.ps.data.Metadata;
import org.uncertweb.ps.encoding.Encoding;
import org.uncertweb.ps.encoding.EncodingRepository;
import org.uncertweb.ps.encoding.binary.AbstractBinaryEncoding;
import org.uncertweb.ps.encoding.xml.AbstractXMLEncoding;
import org.uncertweb.ps.encoding.xml.AbstractXMLEncoding.Include;
import org.uncertweb.ps.process.AbstractProcess;
import org.uncertweb.ps.process.ProcessRepository;
import org.uncertweb.xml.Namespaces;
public class XMLSchemaGenerator {
// FIXME: would be good to cache
private ProcessRepository processRepo;
private EncodingRepository encodingRepo;
public XMLSchemaGenerator() {
processRepo = ProcessRepository.getInstance();
encodingRepo = EncodingRepository.getInstance();
}
public Document generateDocument() {
Element schema = new Element("schema", Namespaces.XSD);
schema.setAttribute("targetNamespace", Namespaces.PS.getURI());
schema.setAttribute("elementFormDefault", "qualified");
// keep things tidy
schema.addNamespaceDeclaration(Namespaces.PS);
schema.addNamespaceDeclaration(Namespaces.XSD);
// get processes
List<AbstractProcess> processes = processRepo.getProcesses();
// make a map for namespace > prefix mapping
Map<String, String> namespaceMapping = new HashMap<String, String>();
namespaceMapping.put(Namespaces.PS.getURI(), "ps");
namespaceMapping.put(Namespaces.XSD.getURI(), "xsd");
int nextNS = 0;
for (AbstractProcess process : processes) {
// TODO: handle encoding not found
for (String inputIdentifier : process.getInputIdentifiers()) {
// get things
Class<?> dataClass = process.getInputDataDescription(inputIdentifier).getType();
Encoding encoding = encodingRepo.getXMLEncoding(dataClass);
if (encoding instanceof AbstractXMLEncoding) {
String namespace = ((AbstractXMLEncoding) encoding).getNamespace();
if (!namespaceMapping.containsKey(namespace)) {
namespaceMapping.put(namespace, "ns" + nextNS);
schema.addNamespaceDeclaration(Namespace.getNamespace("ns" + nextNS, namespace));
nextNS++;
}
}
}
for (String outputIdentifier : process.getOutputIdentifiers()) {
// get things
Class<?> dataClass = process.getOutputDataDescription(outputIdentifier).getType();
Encoding encoding = encodingRepo.getXMLEncoding(dataClass);
if (encoding instanceof AbstractXMLEncoding) {
String namespace = ((AbstractXMLEncoding) encoding).getNamespace();
if (!namespaceMapping.containsKey(namespace)) {
namespaceMapping.put(namespace, "ns" + nextNS);
schema.addNamespaceDeclaration(Namespace.getNamespace("ns" + nextNS, namespace));
nextNS++;
}
}
}
}
// add data reference element
schema.addContent(createDataReferenceElement());
// add process specific info
for (AbstractProcess process : processes) {
// create request element
Element requestSequence = new Element("sequence", Namespaces.XSD);
schema.addContent(new Element("element", Namespaces.XSD)
.setAttribute("name", process.getIdentifier() + "Request")
.addContent(new Element("complexType", Namespaces.XSD)
.addContent(requestSequence)));
/*.addContent(new Element("attribute", Namespaces.XSD)
.setAttribute("name", "asynchronous")
.setAttribute("type", Namespaces.XSD.getPrefix() + ":boolean")
.setAttribute("default", "false"))));*/
// add inputs
for (String inputIdentifier : process.getInputIdentifiers()) {
requestSequence.addContent(createInputElement(schema, namespaceMapping, inputIdentifier, process.getInputDataDescription(inputIdentifier), process.getInputMetadata(inputIdentifier)));
}
// add req outputs
// create requested outputs elements
Element reqOutputsSequence = new Element("sequence", Namespaces.XSD);
requestSequence.addContent(new Element("element", Namespaces.XSD)
.setAttribute("minOccurs", "0")
.setAttribute("name", "RequestedOutputs")
.addContent(new Element("complexType", Namespaces.XSD)
.addContent(reqOutputsSequence)));
// create response
Element responseSequence = new Element("sequence", Namespaces.XSD);
schema.addContent(new Element("element", Namespaces.XSD)
.setAttribute("name", process.getIdentifier() + "Response")
.addContent(new Element("complexType", Namespaces.XSD)
.addContent(responseSequence)));
// for outputs
for (String outputIdentifier : process.getOutputIdentifiers()) {
responseSequence.addContent(createOutputElement(schema, namespaceMapping, outputIdentifier, process.getOutputDataDescription(outputIdentifier), process.getOutputMetadata(outputIdentifier)));
reqOutputsSequence.addContent(new Element("element", Namespaces.XSD)
.setAttribute("name", outputIdentifier)
.setAttribute("minOccurs", "0")
.addContent(new Element("complexType", Namespaces.XSD)
.addContent(new Element("sequence", Namespaces.XSD))
.addContent(new Element("attribute", Namespaces.XSD)
.setAttribute("name", "reference")
.setAttribute("type", Namespaces.XSD.getPrefix() + ":boolean")
.setAttribute("default", "false"))));
}
}
// return document
return new Document(schema);
}
private Element createDataReferenceElement() {
return new Element("element", Namespaces.XSD)
.setAttribute("name", "DataReference")
.addContent(new Element("complexType", Namespaces.XSD)
.addContent(new Element("sequence", Namespaces.XSD))
.addContent(new Element("attribute", Namespaces.XSD)
.setAttribute("name", "href")
.setAttribute("type", Namespaces.XSD.getPrefix() + ":anyURI")
.setAttribute("use", "required"))
.addContent(new Element("attribute", Namespaces.XSD)
.setAttribute("name", "mimeType")
.setAttribute("type", Namespaces.XSD.getPrefix() + ":string")
.setAttribute("use", "required"))
.addContent(new Element("attribute", Namespaces.XSD)
.setAttribute("name", "compressed")
.setAttribute("type", Namespaces.XSD.getPrefix() + ":boolean")
.setAttribute("default", "false")));
}
private Element createInputElement(Element schema, Map<String, String> namespaceMapping, String identifier, DataDescription dataDescription, List<Metadata> metadata) {
return createInputOutputElement(schema, namespaceMapping, identifier, dataDescription, metadata);
}
private Element createOutputElement(Element schema, Map<String, String> namespaceMapping, String identifier, DataDescription dataDescription, List<Metadata> metadata) {
Element element = createInputOutputElement(schema, namespaceMapping, identifier, dataDescription, metadata);
element.setAttribute("minOccurs", "0");
return element;
}
private Element createInputOutputElement(Element schema, Map<String, String> namespaceMapping, String identifier, DataDescription dataDescription, List<Metadata> metadata) {
// get things
Class<?> dataClass = dataDescription.getType();
// add create element
Element element = new Element("element", Namespaces.XSD)
.setAttribute("name", identifier)
.setAttribute("minOccurs", (dataDescription.getMinOccurs() >= 1 ? "1" : "0"))
.setAttribute("maxOccurs", (dataDescription.getMaxOccurs() == Integer.MAX_VALUE ? "unbounded" : String.valueOf(dataDescription.getMaxOccurs())));
// add metadata
addMetadata(element, metadata);
// add type
List<Class<?>> supportedSimpleTypes = Arrays.asList(new Class<?>[] {
Float.class, Double.class, Boolean.class, URL.class, String.class, Integer.class,
Float[].class, Double[].class, Boolean.class, URL[].class, String[].class, Integer[].class, LocalDate.class, LocalDate[].class });
if (supportedSimpleTypes.contains(dataClass)) {
// float, double, boolean, anyURI, string, integer for now...
String xsdType = Namespaces.XSD.getPrefix() + ":";
if (dataClass.equals(URL.class) || dataClass.equals(URL[].class)) {
xsdType += "anyURI";
}
else if (dataClass.equals(LocalDate.class) || dataClass.equals(LocalDate[].class)) {
xsdType += "date";
}
else {
String dataClassName = dataClass.getName();
xsdType += dataClassName.substring(dataClassName.lastIndexOf(".") + 1, dataClassName.length()).toLowerCase();
}
if (xsdType.endsWith(";")) {
xsdType = xsdType.substring(0, xsdType.length() - 1);
}
if (dataClass.isArray()) {
element.addContent(new Element("simpleType", Namespaces.XSD)
.addContent(new Element("list", Namespaces.XSD)
.setAttribute("itemType", xsdType)));
}
else {
element.setAttribute("type", xsdType);
}
}
else {
// add choice
Element choice = new Element("choice", Namespaces.XSD);
element.addContent(new Element("complexType", Namespaces.XSD)
.addContent(choice));
// get encoding
Encoding encoding = encodingRepo.getXMLEncoding(dataClass);
// FIXME: no appropriate encoding will result in encoding == null, should throw an exception for the servlet to catch
if (!(encoding instanceof AbstractBinaryEncoding)) {
// get encoding
AbstractXMLEncoding xmlEncoding = (AbstractXMLEncoding) encoding;
String namespace = xmlEncoding.getNamespace();
// add imports
addSchemaImport(schema, xmlEncoding);
// add reference
Include include = xmlEncoding.getInclude(dataClass);
choice.addContent(new Element("element", Namespaces.XSD)
.setAttribute("ref", namespaceMapping.get(namespace) + ":" + include.getName()));
}
// add reference
choice.addContent(new Element("element", Namespaces.XSD)
.setAttribute("ref", Namespaces.PS.getPrefix() + ":DataReference"));
}
return element;
}
private static void addMetadata(Element element, List<Metadata> metadata) {
if (metadata != null && metadata.size() > 0) {
String metadataText = "";
for (Metadata m : metadata) {
metadataText += "\n@" + m.getKey() + " " + m.getValue();
}
// looks like jdom strips whitespace at start and end of string
element.addContent(new Element("annotation", Namespaces.XSD).addContent(new Element("documentation", Namespaces.XSD).setText(metadataText)));
}
}
private static void addSchemaImport(Element schema, AbstractXMLEncoding encoding) {
// get import namespace and location
String namespace = encoding.getNamespace();
String schemaLocation = encoding.getSchemaLocation();
// only add if there is a schema location
if (schemaLocation != null) {
// find if already imported
List<?> elements = schema.getChildren("import", Namespaces.XSD);
boolean imported = false;
for (int i = 0; i < elements.size(); i++) {
Element imp0rt = (Element) elements.get(i);
if (imp0rt.getAttributeValue("schemaLocation") != null && imp0rt.getAttributeValue("schemaLocation").equals(schemaLocation)) {
imported = true;
break;
}
}
if (!imported) {
schema.addContent(0, new Element("import", Namespaces.XSD)
.setAttribute("namespace", namespace)
.setAttribute("schemaLocation", schemaLocation));
}
}
}
}