/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.schema;
import java.io.IOException;
import org.ws4d.java.constants.SchemaConstants;
import org.ws4d.java.io.xml.ElementParser;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.types.Attributable;
import org.ws4d.java.types.AttributableSupport;
import org.ws4d.java.types.CustomAttributeValue;
import org.ws4d.java.types.QName;
import org.ws4d.java.types.StringAttributeValue;
import org.ws4d.java.util.StringUtil;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
/**
* This class allows object representation of XML Schema elements.
* <p>
* Those elements are part of the XML Schema definition and are used inside WSDL
* documents to describe the content of a message. It is possible to define XML
* Schema structures with the classes {@link Schema}, {@link Element},
* {@link Attribute}, {@link SimpleType}, {@link ComplexType}, {@link Group} and
* {@link AttributeGroup}. This is at least necessary to invoke SOAP operations
* (like used in DPWS).<br />
* An element consists of a qualified name (local part and namespace) and a
* type.
* </p>
* <h3>XML Schema</h3>
* <p>
* XML Schema describes the structure of the content for a XML instance
* document. Each element is linked to a specific data type. XML Schema comes
* with built-in primitive data types like <i>string</i>, <i>boolean</i>,
* <i>decimal</i> and derived data types like <i>byte</i>, <i>int</i>,
* <i>token</i> and <i>positiveInteger</i>. It is also possible to define one's
* own derived data types. An XML Schema could looks like this:
* </p>
*
* <pre>
* <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org">
* <xs:complexType name="personType">
* <xs:sequence>
* <xs:element name="firstname" type="xs:string" />
* <xs:element name="lastname" type="xs:string" />
* <xs:element name="age" type="xs:int" />
* </xs:sequence>
* </xs:complexType>
* <xs:element name="person" type="personType" />
* </xs:schema>
* </pre>
* <p>
* The XML Schema above defines a derived data type called <i>personType</i>
* which contains inner-elements. The derived data type is used by the element
* <i>person</i>. This XML schema allows the creation of the following XML
* instance document:
* </p>
*
* <pre>
* <?xml version="1.0"?>
* <person>
* <firstname>John</firstname>
* <lastname>Doe</lastname>
* <age>66</age>
* </person>
* </pre>
* <p>
* You can learn more about XML Schema at <a
* href="http://www.w3.org/XML/Schema">http://www.w3.org/XML/Schema</a>
* </p>
* <h3>Framework</h3>
* <p>
* If you like to create the element described above, it is necessary to create
* the derived data type too and use the primitive data type <i>string</i>. If
* you can access predefined primitive data types with the
* {@link SchemaUtil#getSchemaType(String)} method.<br />
* The created code should look like this:
* </p>
*
* <pre>
* // get primitive data types
* Type xsString = SchemaUtil.getSchemaType("string");
* Type xsInt = SchemaUtil.getSchemaType("int");
*
* // create inner elements for personType
* Element firstname = new Element(new QName("firstname", "http://www.example.org"), xsString);
* Element lastname = new Element(new QName("lastname", "http://www.example.org"), xsString);
* Element age = new Element(new QName("age", "http://www.example.org"), xsInt);
*
* // create personType and add inner elements
* ComplexType personType = new ComplexType(new QName("personType", "http://www.example.org"), ComplexType.CONTAINER_SEQUENCE);
* personType.addElement(firstname);
* personType.addElement(lastname);
* personType.addElement(age);
*
* // create element
* Element person = new Element(new QName("person", "http://www.example.org"), personType);
* </pre>
*
* <h3>Details</h3>
* <p>
* The following examples will show how to use the element to create different
* XML Schema structures.
* <ul>
* <li>
* <h4>Element reference</h4>
* <ul>
* <li><a
* href="http://www.w3.org/TR/xmlschema11-1/#ref.elt.global">http://www.w3
* .org/TR/xmlschema11-1/#ref.elt.global</a></li>
* <li>XML Schema:
* <p>
* Element references allow to reference global elements.
*
* <pre>
* <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org">
* <xs:element name="a" type="xs:string" />
* <xs:complexType name="b">
* <xs:sequence>
* <xs:element ref="a" />
* </xs:sequence>
* </xs:complexType>
* </xs:schema>
* </pre>
*
* </p>
* </li>
* <li>Framework:
* <p>
*
* <pre>
* // get primitive data types
* Type xsString = SchemaUtil.getSchemaType("string");
*
* // create element a
* Element a = new Element(new QName("a", "http://example.org"), xsString);
*
* // create reference for element a
* Element aref = new Element(a);
*
* // create type b
* ComplexType b = new ComplexType(new QName("b", "http://example.org"), ComplexType.CONTAINER_SEQUENCE);
* b.addElement(aref);
* </pre>
*
* </p>
* </li>
* </ul>
* </li>
* <li>
* <h4>Substitution group</h4>
* <ul>
* <li><a
* href="http://www.w3.org/TR/xmlschema11-1/#sec-cos-equiv-class">http://www
* .w3.org/TR/xmlschema11-1/#sec-cos-equiv-class</a></li>
* <li>XML Schema:
* <p>
*
* <pre>
* <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org">
* <xs:element name="a" type="xs:string" />
* <xs:element name="b" substitutionGroup="a" />
* </xs:schema>
* </pre>
*
* </p>
* </li>
* <li>Framework:
* <p>
*
* <pre>
* // get primitive data types
* Type xsString = SchemaUtil.getSchemaType("string");
*
* // create element a
* Element a = new Element(new QName("a", "http://example.org"), xsString);
*
* // create a substituted element b
* Element b = new Element(new QName("b", "http://example.org"));
* b.setSubstitutionGroup(new QName("a", "http://example.org"));
* </pre>
*
* </p>
* </ul>
* </li>
* </ul>
* </p>
*
* @see Schema
* @see Attribute
* @see SimpleType
* @see ComplexType
* @see Group
* @see AttributeGroup
*/
public class Element extends AnyElement implements Attributable {
static final String TAG_ELEMENT = SCHEMA_ELEMENT;
static final String ATTRIBUTE_NILLABLE = ELEMENT_NILLABLE;
static final String ATTRIBUTE_TYPE = SCHEMA_TYPE;
static final String ATTRIBUTE_SUBSTITUTIONGROUP = SCHEMA_SUBSTITUTIONGROUP;
protected static int count = 0;
protected boolean globalScope = false;
protected QName typeLink = null;
protected Type type = null;
protected QName subtitutionGroup = null;
protected String fixed = null;
protected boolean nillable = false;
private Attributable attributableDelegate;
protected Type localType = null;
/**
* Returns the number of elements created by the framework. This can be used
* for debug purposes.
*
* @return the number of elements created by the framework.
*/
public static int getElementCount() {
return count;
}
static final Element createElement(ElementParser parser, String targetNamespace, Schema schema) throws XmlPullParserException, IOException, SchemaException {
return createElement(parser, targetNamespace, schema, false);
}
static final Element createElement(ElementParser parser, String targetNamespace, Schema schema, boolean globalScope) throws XmlPullParserException, IOException, SchemaException {
HashMap attributes = null;
String eName = null;
String eType = null;
String eRef = null;
String sGroup = null;
String eAbstract = null;
String minOccurs = null;
String maxOccurs = null;
String nil = null;
int c = parser.getAttributeCount();
for (int i = 0; i < c; i++) {
String attributeName = parser.getAttributeName(i);
String attributeNamespace = parser.getAttributeNamespace(i);
String attributeValue = parser.getAttributeValue(i);
if (attributeNamespace == null || "".equals(attributeNamespace)) {
if (ATTRIBUTE_NAME.equals(attributeName)) {
eName = attributeValue;
} else if (ATTRIBUTE_TYPE.equals(attributeName)) {
eType = attributeValue;
} else if (ATTRIBUTE_REF.equals(attributeName)) {
eRef = attributeValue;
} else if (ATTRIBUTE_SUBSTITUTIONGROUP.equals(attributeName)) {
sGroup = attributeValue;
} else if (ATTRIBUTE_ABSTRACT.equals(attributeName)) {
eAbstract = attributeValue;
} else if (AnyElement.ATTRIBUTE_MINOCCURS.equals(attributeName)) {
minOccurs = attributeValue;
} else if (AnyElement.ATTRIBUTE_MAXOCCURS.equals(attributeName)) {
maxOccurs = attributeValue;
} else if (ATTRIBUTE_NILLABLE.equals(attributeName)) {
nil = attributeValue;
} else {
if (attributes == null) {
attributes = new HashMap();
}
attributes.put(new QName(attributeName, attributeNamespace), new StringAttributeValue(attributeValue));
}
} else {
if (attributes == null) {
attributes = new HashMap();
}
attributes.put(new QName(attributeName, attributeNamespace), new StringAttributeValue(attributeValue));
}
}
if (eType != null && eRef != null) {
throw new SchemaException("Cannot create element. Element definition SHOULD NOT have both, type and ref attribute.");
}
Element e = new Element();
e.setParentSchema(schema);
e.globalScope = globalScope;
if (attributes != null) {
e.setAttributes(attributes);
}
/*
* Set element name.
*/
if (eName != null) {
e.setName(new QName(eName, targetNamespace));
}
/*
* Set element minimum occurs.
*/
if (!globalScope && minOccurs != null) {
e.setMinOccurs(Integer.valueOf(minOccurs).intValue());
}
/*
* Set element maximum occurs.
*/
if (!globalScope && maxOccurs != null) {
if (maxOccurs.equals(MAXOCCURS_UNBOUNDED)) {
e.setMaxOccurs(-1);
} else {
e.setMaxOccurs(Integer.valueOf(maxOccurs).intValue());
}
}
if (nil != null) {
if (StringUtil.equalsIgnoreCase(ATTRIBUTE_VALUE_TRUE, nil)) {
e.setNillable(true);
}
}
/*
* Set the abstract attribute. 0 = not abstract 1 = abstract (false) 2 =
* abstract (true)
*/
if (eAbstract != null) {
e.setAbstract(StringUtil.equalsIgnoreCase(ATTRIBUTE_VALUE_TRUE, eAbstract));
}
/*
* Set substitution group.
*/
if (sGroup != null) {
String p = SchemaUtil.getPrefix(sGroup);
String n = SchemaUtil.getName(sGroup);
String ns = parser.getNamespace(p);
e.setSubstitutionGroup(new QName(n, ns));
}
if (eType != null && eRef == null) {
String p = SchemaUtil.getPrefix(eType);
String n = SchemaUtil.getName(eType);
String ns = parser.getNamespace(p);
QName typeName = new QName(n, ns);
if (XMLSCHEMA_NAMESPACE.equals(ns)) {
Type t = SchemaUtil.getType(typeName);
if (t != null) {
e.setType(t);
}
} else {
e.setTypeLink(typeName);
schema.addElementForResolve(e);
}
} else if (eRef != null && eType == null) {
String p = SchemaUtil.getPrefix(eRef);
String n = SchemaUtil.getName(eRef);
String ns = parser.getNamespace(p);
e.setReferenceLink(new QName(n, ns));
schema.addElementForResolve(e);
}
int d = parser.getDepth();
while (parser.nextTag() != XmlPullParser.END_TAG && parser.getDepth() == d + 1) {
/*
* check for inner definitions
*/
String namespace = parser.getNamespace();
String name = parser.getName();
if (XMLSCHEMA_NAMESPACE.equals(namespace)) {
if (StringUtil.equalsIgnoreCase(ComplexType.TAG_COMPLEXTYPE, name)) {
Type t = ComplexType.createComplexType(parser, targetNamespace, schema);
e.setType(t);
} else if (StringUtil.equalsIgnoreCase(SimpleType.TAG_SIMPLETYPE, name)) {
Type t = SimpleType.createSimpleType(parser, targetNamespace, schema);
e.setType(t);
} else if (StringUtil.equalsIgnoreCase(Annotation.TAG_ANNOTATION, name)) {
Annotation.handleAnnotation(parser, e);
}
}
}
return e;
}
Element() {
this((QName) null);
}
/**
* Creates an element with the given name and namespace.
* <p>
* This constructor will generate an appropriate qualified name.
* </p>
*
* @param name the name of the element.
* @param namespace the namespace.
*/
public Element(String name, String namespace) {
this(new QName(name, namespace));
}
/**
* Creates an element with given name, namespace and type.
* <p>
* This constructor will generate an appropriate qualified name.
* </p>
*
* @param name the name of the element.
* @param namespace the namespace.
* @param type the type of the element.
*/
public Element(String name, String namespace, Type type) {
this(new QName(name, namespace), type);
}
/**
* Creates an element with given qualified name.
*
* @param name the qualified name of the element.
*/
public Element(QName name) {
this(name, null);
}
/**
* Creates an element with given element name and type.
*
* @param name the qualified name of the element.
* @param type the type of the element.
*/
public Element(String elementName, Type type) {
this(new QName(elementName, null), type);
}
/**
* Creates an element with given type.
*
* @param name the qualified name of the element.
* @param type the type of the element.
*/
public Element(Type type) {
this((QName) null, type);
}
/**
* Creates an element with given qualified name and type.
*
* @param name the qualified name of the element.
* @param type the type of the element.
*/
public Element(QName name, Type type) {
this.name = name;
if ((name != null && !XMLSCHEMA_NAMESPACE.equals(name.getNamespace())) || name == null) {
count++;
}
if (this.name == null) {
this.name = new QName(StringUtil.simpleClassName(getClass()), null);
}
setType(type);
}
/**
* Creates an element with given name, namespace and type.
* <p>
* This constructor will generated an appropriate qualified name.
* </p>
*
* @param name the name of the element.
* @param namespace the namespace.
* @param type the type of the element.
* @param min the minimum occurrence of this element.
* @param max the maximum occurrence of this element.
*/
public Element(String name, String namespace, Type type, int min, int max) {
this(new QName(name, namespace), type, min, max);
}
/**
* Creates an element with given qualified name, type and occurrences.
*
* @param name the qualified name of the element.
* @param type the type of the element.
* @param min the minimum occurrence of this element.
* @param max the maximum occurrence of this element.
*/
public Element(QName name, Type type, int min, int max) {
this.name = name;
if ((name != null && !XMLSCHEMA_NAMESPACE.equals(name.getNamespace())) || name == null) {
count++;
}
setType(type);
setMinOccurs(min);
setMaxOccurs(max);
}
/**
* Creates a reference based on a specified element.
*
* @param reference the element which should be referenced.
*/
public Element(Element reference) {
this((QName) null);
setReference(reference);
}
/**
* Creates a reference based on a specified element.
*
* @param reference the element which should be referenced.
* @param min the minimum occurrence of this element.
* @param max the maximum occurrence of this element.
*/
public Element(Element reference, int min, int max) {
this((QName) null);
setReference(reference);
setMinOccurs(min);
setMaxOccurs(max);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer(StringUtil.formatClassName(getClass()));
QName name = getName();
if (name != null) {
sb.append(" [ name=").append(name.getLocalPart());
sb.append(", namespace=").append(name.getNamespace());
sb.append(", type=").append(getType().getName());
sb.append(" ]");
}
return sb.toString();
}
/*
* (non-Javadoc)
* @see org.ws4d.java.types.schema.Annotation#getSchemaIdentifier()
*/
public int getSchemaIdentifier() {
return SchemaConstants.XSD_ELEMENT;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.wsdl.Attributable#getAttribute(org.ws4d.java.types.QName)
*/
public CustomAttributeValue getAttribute(QName name) {
return attributableDelegate == null ? null : attributableDelegate.getAttribute(name);
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.types.Attributable#setAttribute(org.ws4d.java.types.QName,
* org.ws4d.java.types.CustomAttributeValue)
*/
public void setAttribute(QName name, CustomAttributeValue value) {
if (attributableDelegate == null) {
attributableDelegate = new AttributableSupport();
}
attributableDelegate.setAttribute(name, value);
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.types.Attributable#setAttribute(org.ws4d.java.types.QName,
* java.lang.String)
*/
public void setAttribute(QName name, String value) {
setAttribute(name, new StringAttributeValue(value));
}
/*
* (non-Javadoc)
* @see org.ws4d.java.wsdl.Attributable#getAttributes()
*/
public HashMap getAttributes() {
if (attributableDelegate == null) {
attributableDelegate = new AttributableSupport();
}
return attributableDelegate.getAttributes();
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.wsdl.Attributable#setAttributes(org.ws4d.java.structures
* .HashMap)
*/
public void setAttributes(HashMap attributes) {
if (attributableDelegate == null) {
if (attributes == null) {
return;
}
attributableDelegate = new AttributableSupport();
}
attributableDelegate.setAttributes(attributes);
}
/*
* (non-Javadoc)
* @see org.ws4d.java.types.Attributable#hasAttributes()
*/
public boolean hasAttributes() {
return attributableDelegate != null && attributableDelegate.hasAttributes();
}
/*
* (non-Javadoc)
* @see org.ws4d.java.types.Attributable#serializeAttributes(org.xmlpull.v1.
* XmlSerializer)
*/
public void serializeAttributes(XmlSerializer serializer) throws IOException {
if (attributableDelegate != null) {
attributableDelegate.serializeAttributes(serializer);
}
}
/**
* Returns the type of this element.
*
* @return the type of this element.
*/
public Type getType() {
if (reference != null) return ((Element) reference).getType();
if (type == null) return (localType != null ? localType : Schema.ANY_TYPE);
return type;
}
/**
* Returns whether this element value is fixed or not.
*
* @return <code>true</code> if the value of this element cannot be changed,
* <code>false</code> otherwise.
*/
public boolean isFixed() {
return (fixed == null);
}
/**
* Returns the fixed value for this element.
* <p>
* The fixed value cannot be changed inside a XML instance document.
* </p>
*
* @return the fixed value for this element.
*/
public String getFixedValue() {
return fixed;
}
/**
* Sets the name of the substitution group for this element.
* <p>
* This affects only elements which are root elements of a XML Schema.
* </p>
*
* @param group the qualified name for the group.
*/
public void setSubstitutionGroup(QName group) {
subtitutionGroup = group;
}
/**
* Returns the name of the substitution group for this element.
*
* @return the qualified name of the group.
*/
public QName getSubstitutionGroup() {
return subtitutionGroup;
}
/**
* Sets the type of the element.
*
* @param type the type of the element.
*/
public void setType(Type type) {
typeLink = null;
abstractValue = false;
localType = null;
this.type = type;
}
protected void setLocalType(Type type) {
// this.type = null;
// localType = type;
}
/**
* Returns whether the instance of this element can handle
* <strong>nil</strong> values or not.
*
* @return <code>true</code> if the instance can handle <strong>nil</strong>
* values, <code>false</code> otherwise.
*/
public boolean isNillable() {
return nillable;
}
/**
* Set whether the instance of this element can handle <strong>nil</strong>
* values or not.
*
* @param nillable <code>true</code> if the instance can handle
* <strong>nil</strong> values, <code>false</code> otherwise.
*/
public void setNillable(boolean nillable) {
this.nillable = nillable;
}
void setTypeLink(QName typeLink) {
this.typeLink = typeLink;
}
QName getTypeLink() {
return typeLink;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.schema.AnyElement#serialize(org.xmlpull.v1.XmlSerializer,
* org.ws4d.java.schema.Schema)
*/
void serialize(XmlSerializer serializer, Schema schema) throws IOException {
serializer.startTag(XMLSCHEMA_NAMESPACE, TAG_ELEMENT);
/*
* THX @Stefan Schlichting: Do not generate name if ref is present see
* http://www.w3.org/TR/xmlschema-1/#d0e4233 2.1
*/
if (name != null && reference == null) {
serializer.attribute(null, ATTRIBUTE_NAME, name.getLocalPart());
}
if (subtitutionGroup != null) {
serializer.attribute(null, ATTRIBUTE_SUBSTITUTIONGROUP, subtitutionGroup.getLocalPart());
}
if (abstractValue) {
serializer.attribute(null, ATTRIBUTE_ABSTRACT, ATTRIBUTE_VALUE_TRUE);
}
if (nillable == true) {
serializer.attribute(null, ATTRIBUTE_NILLABLE, ATTRIBUTE_VALUE_TRUE);
}
if (!globalScope) {
if (min != 1) {
serializer.attribute(null, AnyElement.ATTRIBUTE_MINOCCURS, String.valueOf(min));
}
if (max != 1) {
if (max == -1) {
serializer.attribute(null, AnyElement.ATTRIBUTE_MAXOCCURS, MAXOCCURS_UNBOUNDED);
} else {
serializer.attribute(null, AnyElement.ATTRIBUTE_MAXOCCURS, String.valueOf(max));
}
}
}
if (type != null) {
QName typeName = type.getName();
if (typeName == null) {
serializeAttributes(serializer);
type.serialize(serializer, schema);
} else {
String prefix = serializer.getPrefix(typeName.getNamespace(), false);
if (!(prefix == null || "".equals(prefix))) {
typeName.setPrefix(prefix);
serializer.attribute(null, ATTRIBUTE_TYPE, typeName.getLocalPartPrefixed());
} else {
serializer.attribute(null, ATTRIBUTE_TYPE, typeName.getLocalPart());
}
serializeAttributes(serializer);
}
} else if (reference != null) {
QName refName = reference.getName();
schema.addReferenceElement((Element) reference);
String prefix = serializer.getPrefix(refName.getNamespace(), false);
if (!(prefix == null || "".equals(prefix))) {
refName.setPrefix(prefix);
serializer.attribute(null, ATTRIBUTE_REF, refName.getLocalPartPrefixed());
} else {
serializer.attribute(null, ATTRIBUTE_REF, refName.getLocalPart());
}
serializeAttributes(serializer);
} else {
serializeAttributes(serializer);
}
serializer.endTag(XMLSCHEMA_NAMESPACE, TAG_ELEMENT);
}
}