org.yawlfoundation.yawl.resourcing.util.DataSchemaBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.yawlfoundation.yawl.resourcing.util.DataSchemaBuilder.java

Source

/*
 * Copyright (c) 2004-2012 The YAWL Foundation. All rights reserved.
 * The YAWL Foundation is a collaboration of individuals and
 * organisations who are committed to improving workflow technology.
 *
 * This file is part of YAWL. YAWL 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.
 *
 * YAWL 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with YAWL. If not, see <http://www.gnu.org/licenses/>.
 */

package org.yawlfoundation.yawl.resourcing.util;

import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.yawlfoundation.yawl.elements.data.YParameter;
import org.yawlfoundation.yawl.elements.data.YVariable;
import org.yawlfoundation.yawl.engine.interfce.SpecificationData;
import org.yawlfoundation.yawl.engine.interfce.TaskInformation;
import org.yawlfoundation.yawl.schema.XSDType;
import org.yawlfoundation.yawl.schema.internal.YInternalType;
import org.yawlfoundation.yawl.util.JDOMUtil;

import javax.xml.XMLConstants;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

/**
 * Expands a data schema for a case start or a task to its base xsd types for use in the
 * definition of a dynamic form.
 *
 * Author: Michael Adams
 * Creation Date: 5/04/2010
 */
public class DataSchemaBuilder {

    // a map of user defined type names to their definitions
    private Map<String, Element> _schemaMap;

    // the set of namespaces used by this schema
    private Map<String, Namespace> _nsList;

    /**
     * The constructor.
     * @param schemaMap a map of user defined types of this particular specification
     */
    public DataSchemaBuilder(Map<String, Element> schemaMap) {
        _schemaMap = schemaMap;
        _nsList = new Hashtable<String, Namespace>();
    }

    /**
     * Constructs the expanded data schema, using the user-defined-types map passed
     * in via the constructor
     * @param specData the SpecificationData object for the spec to construct a schema for
     * @return the constructed schema (as a string)
     */
    public String build(SpecificationData specData) {
        return buildSchema(specData.getRootNetID(), specData.getInputParams());
    }

    /**
     * Constructs the expanded data schema, using the user-defined-types map passed
     * in via the constructor
     * @param taskInfo the TaskInformation object for the task to construct a schema for
     * @return the constructed schema (as a string)
     */
    public String build(TaskInformation taskInfo) {
        return buildSchema(taskInfo.getDecompositionID(), taskInfo.getParamSchema().getCombinedParams());
    }

    /**
     * Constructs the expanded data schema, using the user-defined-types map passed
     * in via the constructor.
     * @param rootName the name of the root element (task or root net name)
     * @param parameters the List of parameters to build the schema for
     * @return the constructed schema (as a string)
     */
    public String buildSchema(String rootName, List<? extends YVariable> parameters) {
        Namespace defNS = getDefaultNamespace();

        // create a new schema doc preamble (down to first sequence element)
        Element sequence = createPreamble(rootName, defNS);

        // for each param build an appropriate element
        for (YVariable param : parameters) {
            sequence.addContent(createParamElement(param, defNS));
        }

        return completeSchema(sequence.getDocument());
    }

    /**
     * Constructs a data schema for a single variable name and data type
     * @param rootName the name to give to the root element
     * @param varName the name to give to the data element
     * @param dataType the datatype for the data element
     * @return a schema for the datatype that variables of the name supplied can be
     * validated against
     */
    public String buildSchema(String rootName, String varName, String dataType) {
        Namespace defNS = getDefaultNamespace();

        // create a new schema doc preamble (down to first sequence element)
        Element sequence = createPreamble(rootName, defNS);

        // build an appropriate element for the data type
        sequence.addContent(createDataTypeElement(varName, dataType, defNS));

        return completeSchema(sequence.getDocument());
    }

    private Element createPreamble(String rootName, Namespace defNS) {

        // create a new doc with a root element called 'schema'
        Element root = new Element("schema", defNS);
        root.setAttribute("elementFormDefault", "qualified");
        new Document(root); // attaches a default doc as parent of root element

        // attach an element set to the supplied root name
        Element taskElem = new Element("element", defNS);
        taskElem.setAttribute("name", rootName);
        root.addContent(taskElem);

        Element complex = new Element("complexType", defNS);
        Element sequence = new Element("sequence", defNS);
        taskElem.addContent(complex);
        complex.addContent(sequence);

        return sequence;
    }

    private String completeSchema(Document doc) {

        // add all the namespaces referred to in the schema to the root element
        for (Namespace ns : _nsList.values()) {
            doc.getRootElement().addNamespaceDeclaration(ns);
        }

        return JDOMUtil.documentToString(doc);
    }

    /**
     * Constructs a schema element for a parameter
     * @param param the parameter to construct a schema for
     * @param defNS the default namespace
     * @return the constructed schema for this parameter
     */
    private Element createParamElement(YVariable param, Namespace defNS) {
        Element element = new Element("element", defNS);
        element.setAttribute("name", param.getName());

        // simple types are defined by attribute, user-defined types are defined by
        // sub elements
        String dataType = param.getDataTypeNameUnprefixed();
        if (isXSDType(dataType)) {
            element.setAttribute("type", prefix(dataType, defNS));
        } else if (YInternalType.isType(dataType)) {
            element = YInternalType.getSchemaFor(dataType, param.getName());
        } else {
            element = createComplexType(param, element, defNS);
        }

        // set default min and max occurs for this parameter
        element.setAttribute("minOccurs", param.isOptional() ? "0" : "1");
        element.setAttribute("maxOccurs", "1");

        return element;
    }

    private Element createDataTypeElement(String varName, String dataType, Namespace defNS) {
        Element element = new Element("element", defNS);
        element.setAttribute("name", varName);

        // simple types are defined by attribute, user-defined types are defined by
        // sub elements
        if (isXSDType(dataType)) {
            element.setAttribute("type", prefix(dataType, defNS));
        } else {
            element = cloneUserDefinedType(element, dataType, defNS);
        }

        return element;
    }

    /**
     * Constructs a new complex type schema for a parameter
     * @param param the parameter with a user-defined type definition
     * @param base the base parameter element
     * @param defNS the default namespace
     * @return the constructed complex type schema
     */
    private Element createComplexType(YVariable param, Element base, Namespace defNS) {
        String URI = param.getDataTypeNameSpace();
        if (URI != null) {
            addNamespace(URI, param.getDataTypePrefix()); // add any new namespace
        }
        return cloneUserDefinedType(base, param.getDataTypeName(), defNS);
    }

    /**
     * Clones a user-defined type definition to use within a schema. May be called
     * recursively.
     * @param base the 'parent' element this type is defined for
     * @param type the user-defined type name
     * @param defNS the default namespace
     * @return the user defined type definiton as a schema 
     */
    private Element cloneUserDefinedType(Element base, String type, Namespace defNS) {
        Element udType = new Element("element", defNS);
        udType.setAttribute("name", base.getAttributeValue("name"));
        Element typeDefn = _schemaMap.get(type);

        // set the first element name to the name of the udt's element (eg. sequence)
        Element parent = new Element(typeDefn.getName(), defNS);
        cloneContent(parent, typeDefn, defNS);
        udType.addContent(parent);

        // set min & max occurs for this element (if defined)
        String minOccurs = base.getAttributeValue("minOccurs");
        String maxOccurs = base.getAttributeValue("maxOccurs");
        if (minOccurs != null)
            udType.setAttribute("minOccurs", minOccurs);
        if (maxOccurs != null)
            udType.setAttribute("maxOccurs", maxOccurs);

        return udType;
    }

    /**
     * Clones the content of a user-defined type. May be called recursively.
     * @param base the 'parent' element this content will form the schema for
     * @param toCopy the user defined type definition
     * @param defNS the default namespace
     * @return the cloned content
     */
    private Element cloneContent(Element base, Element toCopy, Namespace defNS) {
        Element newChild;

        for (Element child : toCopy.getChildren()) {
            String type = getTypeNameUnprefixed(child);
            if (type != null) {

                // if there's a type attribute and its a simple type, set it for the clone
                if (isXSDType(type)) {
                    newChild = cloneElement(child, defNS);
                    newChild.setAttribute("type", prefix(type, defNS));
                }

                // if its a udt, recurse to define the udt subtype
                else {
                    newChild = cloneUserDefinedType(child, type, defNS);
                }
            } else {
                newChild = cloneElement(child, defNS);
            }

            // recurse to process this element's children (til there are no more children)
            cloneContent(newChild, child, defNS);

            base.addContent(newChild);
        }
        return base;
    }

    /**
     * Creates a new element with the same name and attributes as the old one
     * @param element the elment to clone
     * @param defNS the default namespace
     * @return the cloned element
     */
    private Element cloneElement(Element element, Namespace defNS) {
        Element cloned = new Element(element.getName(), defNS);
        cloned.setAttributes(cloneAttributes(element, defNS));
        return cloned;
    }

    /**
     * Clones a set of attributes. Needs to be done this way to (i) break the
     * parental attachment to the attribute; and (ii) to fix any errant namespace
     * prefixes
     * @param element the element with the attributes to clone
     * @param defNS the default namespace
     * @return the List of clone attributes
     */
    private List<Attribute> cloneAttributes(Element element, Namespace defNS) {
        String prefix = element.getNamespacePrefix();
        List<Attribute> cloned = new ArrayList<Attribute>();
        for (Attribute attribute : element.getAttributes()) {
            String value = getAttributeValue(attribute, prefix, defNS);
            Attribute copy = new Attribute(attribute.getName(), value);
            cloned.add(copy);
        }
        return cloned;
    }

    /**
     * Gets an attribute value, fixing its namespace prefix (if any)
     * @param attribute the attribute to get the value for
     * @param prefix the correct namespace prefix
     * @param defNS the default namespace
     * @return the attribute's value with the prefix corrected if required
     */
    private String getAttributeValue(Attribute attribute, String prefix, Namespace defNS) {
        String value = attribute.getValue();
        if (prefix.length() > 0) {
            value = value.replaceFirst(prefix, defNS.getPrefix());
        }
        return value;
    }

    /**
     * Checks that a type name is one of the XSD base types
     * @param type the type name to check
     * @return true if it is one of the base XSD type names
     */
    private boolean isXSDType(String type) {
        return XSDType.isBuiltInType(type);
    }

    /**
     * Attaches a prefix to a type name
     * @param type the type name
     * @param ns the namespace
     * @return the type name prefixed with the namespace's prefix
     */
    private String prefix(String type, Namespace ns) {
        return String.format("%s:%s", ns.getPrefix(), type);
    }

    /**
     * Gets the value of an element's type attribute with the namespace prefix removed
     * @param e the element with the type attribute
     * @return the unprefixed type name
     */
    private String getTypeNameUnprefixed(Element e) {
        String typeName = e.getAttributeValue("type");
        if (typeName != null) {
            int pos = typeName.indexOf(":");
            if (pos > -1) {
                typeName = typeName.substring(pos + 1);
            }
        }
        return typeName;
    }

    /**
     * Creates and stores the default XSD namespace
     * @return the default XSD namespace
     */
    private Namespace getDefaultNamespace() {
        Namespace ns = Namespace.getNamespace("xsd", XMLConstants.W3C_XML_SCHEMA_NS_URI);
        _nsList.put(XMLConstants.W3C_XML_SCHEMA_NS_URI, ns);
        return ns;
    }

    /**
     * Adds a new namespace to the list of namespaces ussed by this schema
     * @param URI the namespace's URI
     * @param prefix the namespace's prefix
     * @return the namespace
     */
    private Namespace addNamespace(String URI, String prefix) {
        if ((prefix == null) || prefix.length() == 0)
            return null;
        if (!_nsList.containsKey(URI)) {
            _nsList.put(URI, Namespace.getNamespace(prefix, URI));
        }
        return _nsList.get(URI);
    }

}