/*******************************************************************************
* 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.service.parameter;
import java.io.IOException;
import java.io.OutputStream;
import org.ws4d.java.constants.SchemaConstants;
import org.ws4d.java.constants.XMLConstants;
import org.ws4d.java.io.xml.XmlSerializerImplementation;
import org.ws4d.java.schema.Attribute;
import org.ws4d.java.schema.AttributeGroup;
import org.ws4d.java.schema.ComplexType;
import org.ws4d.java.schema.Element;
import org.ws4d.java.schema.ExtendedComplexContent;
import org.ws4d.java.schema.ExtendedSimpleContent;
import org.ws4d.java.schema.Group;
import org.ws4d.java.schema.Schema;
import org.ws4d.java.schema.SchemaUtil;
import org.ws4d.java.schema.SimpleType;
import org.ws4d.java.schema.Type;
import org.ws4d.java.service.Operation;
import org.ws4d.java.structures.ArrayList;
import org.ws4d.java.structures.EmptyStructures;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.structures.LinkedList;
import org.ws4d.java.structures.List;
import org.ws4d.java.structures.ListIterator;
import org.ws4d.java.structures.ReadOnlyIterator;
import org.ws4d.java.types.QName;
import org.ws4d.java.util.Log;
import org.ws4d.java.util.ParameterUtil;
import org.ws4d.java.util.StringUtil;
import org.xmlpull.v1.XmlSerializer;
/**
* This class allows object representation of XML instance documents.
* <p>
* XML Schema describes the structure of content for XML instance documents.
* Those definitions are used inside WSDL documents to describe a message's
* content. 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 />
* A complex type consists of a qualified name and the description of the
* content structure.
* </p>
* <h3>XML Schema</h3>
* <p>
* XML Schema describes the structure of the content for a XML instance
* document. Each element is dedicated 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 look 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 want to create the complex type 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 <i>person</i> element defined above can be used as <strong>input</strong>
* or <strong>output</strong> parameter of an operation. This will allow to use
* this parameter within a service. As shown in the XML Schema part, an element
* defined inside a XML Schema will be used to create XML instance documents.
* The Framework allows to create those XML instance documents with this class.
* A parameter value can be created from an element with the
* {@link ParameterValue#createElementValue(Element)} method, or will be
* pass-through within action invocation.
* </p>
* <p>
* The <code>ParameterValue</code> class allows nested structures like seen in
* XML. An object of this class represents a single entry in a XML instance
* document. The XML shown above, has an root element named "person" containing
* three inner-elements, firstname, lastname and age.<br />
* This would lead to an parameter value with three nested inner-elements. The
* <code>ParameterValue</code> class allows to access the element directly and
* any inner-element. <strong>To access the value of a parameter it is necessary
* to check the type of the parameter and cast to the correct
* implementation.</strong> The framework comes along with the implementation of
* xs:string {@link StringValue}, xs:QNAME {@link QNameValue} and
* xs:base64binary {@link AttachmentValue}. It is possible to register own
* implementation of XML Schema datatypes. If no implementation matches the
* given data type a it will be handles as xs:string (fallback). The following
* lines of code, will show the usage for the structure defined above:
* </p>
*
* <pre>
* // create ParameterValue from element
* ParameterValue personInstance = ParameterValue.createElementValue(person);
*
* // as person does not have any values to set, set the value of the
* // inner-elements.
* // direct access using the path (something like XPath).
* ParameterValue fname = personInstance.get("firstname");
* ParameterValue lname = personInstance.setValue("lastname");
* ParameterValue a = personInstance.setValue("age");
*
* // check for correct type, cast and set the value
* if (fname.getValueType() == ParameterValue.TYPE_STRING) {
* StringValue firstname = (StringValue) fname;
* // set value for the string
* firstname.set("John");
* }
*
* if (lname.getValueType() == ParameterValue.TYPE_STRING) {
* StringValue lastname = (StringValue) lname;
* // set value for the string
* lastname.set("Doe");
* }
*
* // As there is not implementation for xs:integer we must use the xs:string
* // fallback here
* if (a.getValueType() == ParameterValue.TYPE_STRING) {
* StringValue age = (StringValue) a;
* // set value for the string
* lastname.set("66");
* }
*
* // check for correct type, cast and set the value
* if (fname.getValueType() == ParameterValue.TYPE_STRING) {
* StringValue firstname = (StringValue) fname;
* // set value for the string
* String fn = firstname.get();
* }
* </pre>
* <p>
* The <strong>path</strong> value used in different methods, allows direct
* access the inner-elements. Let us assume the XML content below:
* </p>
*
* <pre>
* <?xml version="1.0"?>
* <person>
* <firstname>John</firstname>
* <lastname>Doe</lastname>
* <age>66</age>
* <address>
* <street>Mainstreet 20</firstname>
* <city>Los Wochos</lastname>
* <phone>555-123-780-JOHNDOE</phone>
* <phone>555-123-780-XML</phone>
* </address>
* </person>
* </pre>
* <p>
* To access the elements like street, or even the both phone elements, it
* necessary to extend the path. The path is always relative to the current
* element. Every next entry in the path is divided by a slash (\). No set path
* points the current element. You can use the {@link #setValue(String)} and
* {@link #getValue()} methods for direct access without path. If an entry
* exists more then once, like the phone element in the example above, an
* specific element can be accessed by using an index. The index starts with 0.
* Omitting the index is like using 0.
* </p>
* <p>
* <strong>path syntax:</strong>
* child[index]/child-from-child[index]/child-from-child-from-chil[index]/ ...
* and so on.
* </p>
*
* <pre>
* // create ParameterValue from element
* ParameterValue personInstance = ParameterValue.createElementValue(person);
*
* // as person does not have any values to set, set the value of the
* // inner-elements.
* // direct access using the path (something like XPath).
* personInstance.get("firstname");
* </pre>
*
* <h3>Notice</h3>
* <p>
* The {@link ParameterUtil} class offers shortcut methods for the most common
* cast, get and set operations for the build-in implementation of datatypes.
* </p>
*
* @see Element
* @see Operation
* @see StringValue
* @see QNameValue
* @see AttachmentValue
* @see ParameterUtil
*/
public class ParameterValue {
public static final boolean ALLOW_NOINDEX = true;
public static final int TYPE_UNKNOWN = -1;
public static final int TYPE_COMPLEX = 0;
public static final int TYPE_STRING = 1;
protected static final String TYPE_STRING_CLASS = "org.ws4d.java.service.parameter.StringValue";
public static final int TYPE_ATTACHMENT = 2;
protected static final String TYPE_ATTACHMENT_CLASS = "org.ws4d.java.service.parameter.AttachmentValue";
public static final int TYPE_QNAME = 3;
protected static final String TYPE_QNAME_CLASS = "org.ws4d.java.service.parameter.QNameValue";
protected String override = null;
protected Type type = null;
protected Type instanceType = null;
protected int min = 1;
protected int max = 1;
protected boolean nil = false;
protected QName name = null;
protected List children = EmptyStructures.EMPTY_LIST;
protected HashMap attributes = EmptyStructures.EMPTY_MAP;
protected HashMap namespaceCache = null;
/**
* This map contains mappings from XML Schema datatypes to the classes which
* will be loaded at runtime. <Type, String>
*/
protected static final HashMap registeredValues = new HashMap();
static {
registeredValues.put(SchemaUtil.getSchemaType(SchemaUtil.TYPE_STRING), TYPE_STRING_CLASS);
registeredValues.put(SchemaUtil.getSchemaType(SchemaUtil.TYPE_BASE64_BINARY), TYPE_ATTACHMENT_CLASS);
registeredValues.put(SchemaUtil.getSchemaType(SchemaUtil.TYPE_QNAME), TYPE_QNAME_CLASS);
}
/**
* Returns the namespaces used by this parameter value.
* <p>
* This method allows to collect all namespaces and use it if necessary.
* </p>
*
* @return a {@link List} of {@link QName}.
*/
public List getNamespaces() {
List ns = new LinkedList();
ns.add(name);
if (attributes != EmptyStructures.EMPTY_MAP) {
Iterator it = attributes.values().iterator();
while (it.hasNext()) {
ParameterAttribute pa = (ParameterAttribute) it.next();
ns.add(pa.getName());
}
}
return ns;
}
/**
* Register a class for a XML Schema datatype {@link Type}.
*
* @param type the type which should be used.
* @param clazz the class which should be used to handle that type.
* @return the classname if already set.
*/
public static String register(Type type, String clazz) {
return (String) registeredValues.put(type, clazz);
}
/**
* Unregister a class for a XML Schema datatype {@link Type}.
*
* @param type the type which should be used.
* @return the classname.
*/
public static String unregister(Type type) {
return (String) registeredValues.remove(type);
}
/**
* Returns the VALUE TYPE for this parameter.
* <p>
* A VALUE TYPE should be a unique representation of a
* {@link ParameterValue} implementation which allows to identify the
* implementation and cast correctly.
*
* @return the VALUE TYPE.
*/
public int getValueType() {
return TYPE_COMPLEX;
}
/**
* Allows to override the serialization of this parameter.
* <p>
* <h3>NOTICE:</h3> The given <code>String</code> can contain anything but
* SHOULD contain correct XML data. <strong>This method should be used for
* debug purposes.</strong> A nested parameter can be overriden too.
* </p>
* <p>
* Set to <code>null</code> to disable the override.
* </p>
*
* @param value the value which should override the parameter serialization,
* or <code>null</code> if the parameter should not be
* overridden.
*/
public void overrideSerialization(String value) {
override = value;
}
/**
* Returns whether this parameter value is overridden or not.
*
* @return <code>true</code> the parameter serialization is overridden,
* <code>false</code> otherwise.
*/
public boolean isOverriden() {
return (override != null);
}
/**
* Sets the value of an attribute of this parameter value with given value.
*
* @param attribute the name of the attribute.
* @param value the value of the attribute.
*/
public void setAttributeValue(String attribute, String value) {
ParameterAttribute a = (ParameterAttribute) attributes.get(attribute);
if (a == null) {
/*
* Use no namespace [null], or use the namespace
* [name.getNamepsace()] from this parameter?
*/
a = new ParameterAttribute(new QName(attribute, null));
add(a);
}
a.setValue(value);
}
/**
* Returns the value of an attribute for this parameter value.
*
* @param attribute the attribute to get the value of.
* @return the value of the attribute.
*/
public String getAttributeValue(String attribute) {
if (!hasAttributes()) return null;
ParameterAttribute a = (ParameterAttribute) attributes.get(attribute);
if (a == null) return null;
return a.getValue();
}
public void add(ParameterAttribute attribute) {
if (attributes == EmptyStructures.EMPTY_MAP) {
attributes = new HashMap();
}
attributes.put(attribute.getName().getLocalPart(), attribute);
}
public void addAnyAttribute(QName name, String value) {
ParameterAttribute attribute = new ParameterAttribute(name);
attribute.setValue(value);
add(attribute);
}
/**
* Returns <code>true</code> if this parameter value has attributes,
* <code>false</code> otherwise.
*
* @return <code>true</code> if this parameter value has attributes,
* <code>false</code> otherwise.
*/
public boolean hasAttributes() {
if (attributes == null || attributes.size() == 0) return false;
return true;
}
/**
* Returns an iterator of attributes for this parameter value.
*
* @return an iterator of attributes for this parameter value.
*/
public Iterator attributes() {
return attributes.values().iterator();
}
/**
* Returns an iterator over the qualified names of all attributes within
* this parameter value.
*
* @return an iterator over {@link QName} instances, which represent the
* names of this parameter value's attributes
*/
public Iterator attributeNames() {
List l = new ArrayList(attributes.size());
for (Iterator it = attributes.values().iterator(); it.hasNext();) {
ParameterAttribute attribute = (ParameterAttribute) it.next();
l.add(attribute.getName());
}
return new ReadOnlyIterator(l);
}
/**
* Set the name of this parameter value.
*
* @param name the name.
*/
void setName(QName name) {
this.name = name;
}
/**
* Set the type of this parameter value.
*
* @param type the type.
*/
void setType(Type type) {
this.type = type;
}
public void setInstanceType(Type instanceType) {
this.instanceType = instanceType;
}
/**
* Set whether this parameter should carry values or not.
*
* @param nil <code>true</code> this parameter will not have any values and
* the XML instance nil will be set.
* <strong>xsi:nil="true"</strong>
*/
public void setNil(boolean nil) {
this.nil = nil;
}
/**
* Returns whether the XML instance <strong>nil</strong> value is set or
* not.
*
* @return <code>true</code> if the XML instance <strong>nil</strong> value
* is set, <code>false</code> otherwise.
*/
public boolean isNil() {
return nil;
}
/**
* Returns the type of this parameter value.
*
* @return the parameter value.
*/
public Type getType() {
return type;
}
/**
* Returns the instance type of this parameter value (in accordance to
* xsi:Type attribute). If no instance type is set, the declared type is
* returned.
*
* @return the instance type of the parameter value.
*/
public Type getInstanceType() {
return instanceType == null ? getType() : instanceType;
}
void setMinOccurs(int min) {
this.min = min;
}
/**
* Returns the the minimum occurrence for this parameter value.
* <p>
* The "minOccurs" attribute in XML Schema describes the minimum occurrence
* of this element inside the created XML instance document.
* </p>
*
* @return the minimum occurrence of this parameter value.
*/
public int getMinOccurs() {
return min;
}
void setMaxOccurs(int max) {
this.max = max;
}
/**
* Returns the the maximum occurrence for this parameter value.
* <p>
* The "maxOccurs" attribute in XML Schema describes the maximum occurrence
* of this element inside the created XML instance document.
* </p>
*
* @return the maximum occurrence of this parameter value.
*/
public int getMaxOccurs() {
return max;
}
/**
* Returns the name of the parameter value. The name of the parameter value
* is the name of the entry inside the XML document.
*
* @return the parameter value name
*/
public QName getName() {
return name;
}
ParameterValue getChild(QName name, int index, boolean reset) {
if (name == null || index < 0) return null;
Iterator it = children.iterator();
int i = -1;
while (it.hasNext()) {
ParameterValue child = (ParameterValue) it.next();
if (child.getName().equals(name)) {
i++;
} else if (reset) {
i = -1;
}
if (i == index) {
return child;
}
}
return null;
}
int countChildren(QName name, boolean reset) {
if (name == null) return 0;
Iterator it = children.iterator();
int i = 0;
while (it.hasNext()) {
ParameterValue child = (ParameterValue) it.next();
if (child.getName().equals(name)) {
i++;
} else if (reset) {
i = -1;
}
}
return i;
}
/**
* Adds an inner-element to this parameter value. This method is necessary
* to create nested structures.
*
* @param value the parameter value to add.
*/
public synchronized void add(ParameterValue value) {
if (children == EmptyStructures.EMPTY_LIST) {
children = new LinkedList();
}
namespaceCache = null;
children.add(value);
}
public synchronized void remove(ParameterValue value) {
if (children != EmptyStructures.EMPTY_LIST) {
children.remove(value);
}
namespaceCache = null;
}
/**
* Returns <code>true</code> if this parameter value has inner-elements,
* <code>false</code> otherwise.
*
* @return <code>true</code> if this parameter value has inner-elements,
* <code>false</code> otherwise.
*/
public boolean hasChildren() {
if (children == null || children.size() == 0) return false;
return true;
}
/**
* Returns the number of inner-elements for the parameter value given by the
* path.
*
* @param path the path to access the inner-element.
* @return the amount of inner-elements.
*/
public int getChildrenCount(String path) {
Iterator it = getChildren(this, path);
if (it == null) return 0;
int i = 0;
while (it.hasNext()) {
it.next();
i++;
}
return i;
}
/**
* Returns the number of inner-elements for the parameter value.
*
* @param path the path to access the inner-element.
* @return the amount of inner-elements.
*/
public int getChildrenCount() {
return children.size();
}
/**
* Returns an iterator of inner-elements for the parameter value given by
* the path.
*
* @param path the path to access the inner-element.
* @return iterator of inner-elements.
*/
public Iterator getChildren(String path) {
return getChildren(this, path);
}
Iterator getChildren(ParameterValue wVal, String path) {
ParameterPath pp = new ParameterPath(path);
if (pp.getDepth() == 0) {
return null;
}
int depth = 0;
Type t = wVal.getInstanceType();
if (!(t.isComplexType())) {
return EmptyStructures.EMPTY_ITERATOR;
}
String node = pp.getNode(depth);
int index = pp.getIndex(depth);
String npath = pp.getPath(depth + 1);
String namespace = wVal.name.getNamespace();
QName search = new QName(node, namespace);
if (pp.getDepth() > 1) {
ParameterValue child = wVal.getChild(search, index, false);
if (child == null) {
return null;
}
return getChildren(child, npath);
} else {
ArrayList list = new ArrayList(1);
int i = wVal.countChildren(search, false);
for (int j = 0; j < i; j++) {
ParameterValue child = wVal.getChild(search, j, false);
list.add(child);
}
return list.iterator();
}
}
/**
* Returns <code>true</code> if this parameter value is based on a complex
* type, <code>false</code> otherwise.
*
* @return <code>true</code> if this parameter value is based on a complex
* type, <code>false</code> otherwise.
*/
public boolean hasChildrenFromType() {
if (type == null) return false;
if (type instanceof ComplexType) {
ComplexType complex = (ComplexType) type;
return complex.hasElements();
}
return false;
}
/**
* Returns an iterator of types for all inner-elements.
*
* @return an iterator of types for all inner-elements.
*/
public Iterator childrenFromType() {
if (type == null) return EmptyStructures.EMPTY_ITERATOR;
if (type.isComplexType()) {
List list = new LinkedList();
ComplexType complex = (ComplexType) type;
for (Iterator it = complex.elements(); it.hasNext();) {
Element e = (Element) it.next();
ParameterValue pv = ParameterValue.createElementValue(e);
pv.setMaxOccurs(e.getMaxOccurs());
pv.setMinOccurs(e.getMinOccurs());
list.add(pv);
}
return list.iterator();
}
return EmptyStructures.EMPTY_ITERATOR;
}
/**
* Returns an iterator of inner-elements for this parameter value.
*
* @return an iterator of inner-elements for this parameter value.
*/
public Iterator children() {
return children.iterator();
}
/**
* Returns an listiterator of inner-elements for this parameter value.
*
* @return an listiterator of inner-elements for this parameter value.
*/
public ListIterator getChildrenList() {
return children.listIterator();
}
/**
* Resolve the types based on the given XML schema.
*
* @param s the XML schema which contains the types for this parameter
* value.
*/
public void resolveTypes(Schema s) {
Element e = s.getElement(name);
if (e != null) {
Type t = e.getType();
type = t;
} else {
return;
}
Iterator it = children();
while (it.hasNext()) {
ParameterValue child = (ParameterValue) it.next();
if (child.hasChildren()) {
child.resolveType((ComplexType) type, s);
}
}
}
public ParameterValue removeChild(String path) {
ParameterValue pv = get(this, null, path, false);
if (pv != null && !this.equals(pv)) {
remove(pv);
}
return pv;
}
public ParameterValue createChild(String path) {
ParameterValue pv = get(this, null, path, true);
return pv;
}
public ParameterValue createChild(String path, Type instanceType) {
return get(this, null, path, instanceType, true);
}
/**
* Serializes the parameter value with a XML serializer.
*
* @param serializer the XML serializer.
* @throws IOException throws an exception if the parameter value could not
* be serialized correctly.
*/
protected void serialize(XmlSerializer serializer, HashMap nsCache) throws IOException {
if (override != null) {
/*
* Override the given parameter. This serializes the given string
* and not the content of the parameter.
*/
serializer.ignorableWhitespace(override);
return;
} else {
serialize0(serializer, nsCache);
}
}
/**
* Serializes the parameter value with a XML serializer.
*
* @param serializer the XML serializer.
* @throws IOException throws an exception if the parameter value could not
* be serialized correctly.
*/
public void serialize(XmlSerializer serializer) throws IOException {
if (override != null) {
/*
* Override the given parameter. This serializes the given string
* and not the content of the parameter.
*/
serializer.ignorableWhitespace(override);
return;
} else {
serialize0(serializer, namespaceCache);
}
}
/**
* Serializes the parameter value into an XML instance document on a given
* stream.
*
* @param out the stream to serialize to.
* @throws IOException throws an exception if the parameter value could not
* be serialized correctly.
*/
public void serialize(OutputStream out) throws IOException {
XmlSerializer serializer = new XmlSerializerImplementation();
serializer.setOutput(out, XMLConstants.ENCODING);
// serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
// true);
serializer.startDocument(XMLConstants.ENCODING, null);
serialize0(serializer, namespaceCache);
serializer.endDocument();
}
/**
* Resolve the children based on the root complex type.
*
* @param ct the complex type.
*/
private void resolveType(ComplexType ct, Schema s) {
Element e = searchElement(ct, name);
if (e == null) {
if (ct.getName() != null && ct.getName().equals(new QName(SchemaUtil.TYPE_ANYTYPE, SchemaConstants.XMLSCHEMA_NAMESPACE))) {
if (s != null) {
/*
* search inside linked schema
*/
e = s.getElement(name);
if (e == null) {
Log.error("Cannot resolve type in schema. Element not found. (type= " + ct + ", element name=" + name + ", schema=" + s + ")");
}
} else {
Log.error("Cannot resolve type in any type. Element not found. (type= " + ct + ", element name=" + name + ", schema=" + s + ")");
}
} else {
Log.error("Cannot resolve type. Element not found. (type= " + ct + ", element name=" + name + ", schema=" + s + ")");
return;
}
}
Type t = e.getType();
type = t;
Iterator it = children();
while (it.hasNext()) {
ParameterValue child = (ParameterValue) it.next();
if (child.hasChildren()) {
child.resolveType((ComplexType) type, s);
}
}
}
/**
* Resolve the element for the given type. Check for extended content, maybe
* the element was defined somewhere else.
*
* @param t the type to check.
* @param name the name of the element to find.
* @return the element, or <code>null</code> if no element found.
*/
protected static Element searchElement(ComplexType t, QName name) {
if (t == null) return null;
Element e = t.getElementByName(name);
if (e != null) return e;
if (t.getSchemaIdentifier() == SchemaConstants.XSD_EXTENDEDCOMPLEXCONTENT) {
ExtendedComplexContent ect = (ExtendedComplexContent) t;
Type base = ect.getBase();
int i = base.getSchemaIdentifier();
if (i == SchemaConstants.XSD_EXTENDEDCOMPLEXCONTENT || i == SchemaConstants.XSD_RESTRICTEDCOMPLEXCONTENT || i == SchemaConstants.XSD_COMPLEXTYPE) {
e = searchElement((ComplexType) base, name);
}
}
return e;
}
/**
* Resolve the element for the given type. Check for extended content, maybe
* the element was defined somewhere else.
* <p>
* This method will <strong>NOT</strong> check the namespace of the element.
* This allows to search an element in other namespaces.
* </p>
*
* @param t the type to check.
* @param name the name of the element to find.
* @return the element, or <code>null</code> if no element found.
*/
protected static Element searchElementNamespaceless(ComplexType t, String name) {
Element e = t.getElementByName(name);
if (e != null) return e;
if (t.getSchemaIdentifier() == SchemaConstants.XSD_EXTENDEDCOMPLEXCONTENT) {
ExtendedComplexContent ect = (ExtendedComplexContent) t;
Type base = ect.getBase();
int i = base.getSchemaIdentifier();
if (i == SchemaConstants.XSD_EXTENDEDCOMPLEXCONTENT || i == SchemaConstants.XSD_RESTRICTEDCOMPLEXCONTENT || i == SchemaConstants.XSD_COMPLEXTYPE) {
e = searchElementNamespaceless((ComplexType) base, name);
}
}
return e;
}
/**
* The main serialize method. This method serializes the parameter.
*
* @param serializer
* @param nsCache
* @throws IOException
*/
protected synchronized void serialize0(XmlSerializer serializer, HashMap nsCache) throws IOException {
if (nsCache == null) {
namespaceCache = collectNamespaces(serializer);
nsCache = namespaceCache;
}
serializeStartTag(serializer, nsCache);
serializeAttributes(serializer);
serializeChildren(serializer, nsCache);
serializeEndTag(serializer);
}
protected final HashMap collectNamespaces(XmlSerializer serializer) {
HashMap ns = new HashMap();
ParameterValue[] nodes = { this };
collectNamespaces(ns, nodes, serializer.getDepth());
HashMap nsSerialization = new HashMap();
Iterator it = ns.entrySet().iterator();
while (it.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) it.next();
QName namespace = (QName) entry.getKey();
ParameterValue[] p = (ParameterValue[]) ns.get(namespace);
List l = (List) nsSerialization.get(p[p.length - 1]);
if (l == null) {
l = new LinkedList();
l.add(namespace);
nsSerialization.put(p[p.length - 1], l);
} else if (!l.contains(namespace)) {
l.add(namespace);
}
}
return nsSerialization;
}
protected final void collectNamespaces(HashMap namespaces, ParameterValue[] nodes, int depth) {
List ns = getNamespaces();
Iterator it = ns.iterator();
while (it.hasNext()) {
QName n = (QName) it.next();
ParameterValue[] on = (ParameterValue[]) namespaces.get(n);
if (on == null) {
namespaces.put(n, nodes);
} else {
int min = Math.min(on.length, nodes.length);
for (int i = 0; i < min; i++) {
if (!on[i].equals(nodes[i])) {
ParameterValue[] nn = new ParameterValue[i];
System.arraycopy(on, 0, nn, 0, i);
namespaces.put(n, nn);
break;
}
}
}
}
if (hasChildren()) {
for (Iterator children = children(); children.hasNext();) {
ParameterValue[] nn = new ParameterValue[nodes.length + 1];
System.arraycopy(nodes, 0, nn, 0, nodes.length);
ParameterValue child = (ParameterValue) children.next();
nn[nodes.length] = child;
child.collectNamespaces(namespaces, nn, depth + 1);
}
}
}
protected final void serializeStartTag(XmlSerializer serializer, HashMap nsCache) throws IOException {
List l = (List) nsCache.get(this);
if (l != null) {
Iterator it = l.iterator();
while (it.hasNext()) {
QName namespace = (QName) it.next();
String ns = namespace.getNamespace();
if (ns == null || "".equals(ns)) {
continue;
}
String prefix = serializer.getPrefix(ns, false);
if (prefix == null) {
serializer.setPrefix(namespace.getPrefix(), namespace.getNamespace());
}
}
}
serializer.startTag(getName().getNamespace(), getName().getLocalPart());
if (isNil()) {
serializer.attribute(SchemaConstants.XSI_NAMESPACE, SchemaConstants.ATTRIBUTE_XSINIL, "true");
}
if (instanceType != null && instanceType != type) {
QName qn = instanceType.getName();
String prefix = serializer.getPrefix(qn.getNamespace(), true);
if (prefix == null) {
serializer.attribute(SchemaConstants.XSI_NAMESPACE, SchemaConstants.ATTRIBUTE_XSITYPE, qn.getLocalPart());
} else {
qn.setPrefix(prefix);
serializer.attribute(SchemaConstants.XSI_NAMESPACE, SchemaConstants.ATTRIBUTE_XSITYPE, qn.getLocalPartPrefixed());
}
}
}
protected final void serializeEndTag(XmlSerializer serializer) throws IOException {
serializer.endTag(getName().getNamespace(), getName().getLocalPart());
}
protected final void serializeAttributes(XmlSerializer serializer) throws IOException {
if (hasAttributes()) {
for (Iterator it = attributes(); it.hasNext();) {
ParameterAttribute attribute = (ParameterAttribute) it.next();
String value = attribute.getValue();
if (value != null) {
serializer.attribute(attribute.getName().getNamespace(), attribute.getName().getLocalPart(), attribute.getValue());
}
}
}
}
protected final void serializeChildren(XmlSerializer serializer, HashMap nsCache) throws IOException {
if (hasChildren()) {
for (Iterator it = children(); it.hasNext();) {
ParameterValue child = (ParameterValue) it.next();
child.serialize(serializer, nsCache);
}
}
}
public ParameterValue get(String path) throws IndexOutOfBoundsException, IllegalArgumentException {
return ParameterValue.get(this, null, path, false);
}
/**
* Creates an XML instance document representation from a given XML Schema
* element.
*
* @param element the element to create the representation from.
* @return the XML instance document representation.
*/
public static ParameterValue createElementValue(Element element) {
return createElementValue(element, null);
}
public static ParameterValue createElementValue(Element element, Type instanceType) {
if (element == null) {
return null;
}
Type tmpType = instanceType == null ? element.getType() : instanceType;
ParameterValue pVal = null;
if (tmpType.isComplexType()) {
pVal = new ParameterValue();
} else {
pVal = load(tmpType);
}
pVal.setMaxOccurs(element.getMaxOccurs());
pVal.setMinOccurs(element.getMinOccurs());
pVal.setName(element.getName());
pVal.setType(element.getType());
pVal.setInstanceType(instanceType);
addAttributesFromType(pVal, tmpType);
return pVal;
}
protected static Type addAttributesFromType(ParameterValue pv, Type type) {
while (type != null) {
for (Iterator atts = type.allAttributes(); atts.hasNext();) {
Attribute att = (Attribute) atts.next();
Type attType = att.getType();
ParameterAttribute pAtt = new ParameterAttribute(att.getName());
if (att.getDefault() != null) {
pAtt.setValue(att.getDefault());
}
pAtt.setType(attType);
pv.add(pAtt);
}
int schemaId = type.getSchemaIdentifier();
if (schemaId == SchemaConstants.XSD_EXTENDEDCOMPLEXCONTENT) {
type = ((ExtendedComplexContent) type).getBase();
} else if (schemaId == SchemaConstants.XSD_EXTENDEDSIMPLECONTENT) {
type = ((ExtendedSimpleContent) type).getBase();
} else {
type = null;
}
}
return type;
}
protected static ParameterValue load(Type t) {
String className = (String) registeredValues.get(t);
if (className == null) {
// Log.warn("Cannot load value interpreter. Type " + t.getName() +
// " does not match. Using " +
// SchemaUtil.getSchemaType(SchemaUtil.TYPE_STRING) + " instead.");
className = TYPE_STRING_CLASS;
}
ParameterValue v = null;
Class clazz;
try {
clazz = Class.forName(className);
v = (ParameterValue) clazz.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot load value interpreter. Class " + className + " not found.");
} catch (InstantiationException e) {
throw new RuntimeException("Cannot load value interpreter. Cannot create object for " + className + ".");
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot load value interpreter. Access not allowed for " + className + ".");
}
return v;
}
private static ParameterValue get(ParameterValue wVal, ParameterValue parent, String path, boolean cr) throws IndexOutOfBoundsException, IllegalArgumentException {
return get(wVal, parent, path, null, cr);
}
private static ParameterValue get(ParameterValue wVal, ParameterValue parent, String path, Type instanceType, boolean cr) throws IndexOutOfBoundsException, IllegalArgumentException {
if (path == null) return wVal;
ParameterPath pp = wVal.new ParameterPath(path);
if (pp.getDepth() == 0) {
return wVal;
}
int depth = 0;
Type t = wVal.getInstanceType();
if (!(t.isComplexType())) {
return wVal;
}
String node = pp.getNode(depth);
int index = pp.getIndex(depth);
String npath = pp.getPath(depth + 1);
ComplexType complex = (ComplexType) t;
String namespace = wVal.name.getNamespace();
QName search = new QName(node, namespace);
/*
* Possibility check. An element should exists inside the underlying
* container, or we cannot add it here.
*/
Element e = searchElement(complex, search);
if (e == null) {
if (complex.getName() != null && complex.getName().equals(new QName(SchemaUtil.TYPE_ANYTYPE, SchemaConstants.XMLSCHEMA_NAMESPACE))) {
/*
* is ANY type
*/
if (parent != null) {
Type pT = parent.getType();
if (pT == null) {
throw new IllegalArgumentException("Parent parameter has no type set! parent=" + parent);
}
/*
* TODO 13.05.2011: We should create a schema repository
* ... the definition for the searched type can be part
* of any schema we ever used within a service.
*/
Schema s = pT.getParentSchema();
if (s != null) {
/*
* search inside linked schema
*/
e = s.getElement(search);
}
}
} else {
String n = search.getLocalPart();
e = searchElementNamespaceless(complex, n);
}
}
if (e == null) {
throw new IndexOutOfBoundsException("No child found. Missing: " + node);
}
int eMin = e.getMinOccurs();
int eMax = e.getMaxOccurs();
Type eType = e.getType();
int cMax = complex.getContainerMaxOccurs();
int dMax = -1;
/*
* It is necessary to check the occurrence from both, the element itself
* and the model containing this element. Maybe an element is listed
* twice (occurrence=2) with an maximum occurrence 1, but the model has
* maximum occurrence 5. In this case the element can have occurrence 2,
* because the model can exits 5 times.
*/
if (eMax == -1 && cMax == -1) dMax = -1;
if ((eMax == 0 && cMax == -1) || (eMax == -1 && cMax == 0)) dMax = 0;
if (eMax >= 1 && cMax >= 1) dMax = eMax * cMax;
if ((index + 1) > dMax && dMax != -1) {
throw new IndexOutOfBoundsException("Cannot create child. index=" + index + ", max=" + dMax + ", model and element occurrence.");
}
ParameterValue child = null;
int c = wVal.countChildren(search, false);
boolean hIndex = pp.hasIndex(depth);
if (ALLOW_NOINDEX && !hIndex && pp.getDepth() == 1 && cr) {
index += c;
}
if (index < wVal.getChildrenCount()) {
child = wVal.getChild(search, index, false);
} else {
int diff = index - wVal.getChildrenCount();
for (int i = 0; i < diff; i++) {
ParameterValue bastard = ParameterValue.createElementValue(e, instanceType);
wVal.add(bastard);
}
child = ParameterValue.createElementValue(e, instanceType);
wVal.add(child);
}
if (child == null) {
/*
* okay, no child with this name found. we need to create the whole
* structure...
*/
child = ParameterValue.createElementValue(e, instanceType);
wVal.add(child);
}
if (child.getType() != null) {
if (child.getType() != e.getType()) throw new IllegalArgumentException("Type mismatch for " + node);
} else {
Type inType = e.getType();
child.setType(inType);
child.setMaxOccurs(eMax);
child.setMinOccurs(eMin);
inType = addAttributesFromType(child, inType);
}
if (pp.getDepth() > 1) {
return get(child, wVal, npath, cr);
}
return child;
}
/**
* Returns the number of <em>direct</em> children of <code>pv</code> with a
* local name of <code>childLocalName</code>. Returns <code>0</code>, if
* either <code>pv</code> or <code>childLocalName</code> are
* <code>null</code>.
*
* @param pv the parameter value instance, which of to count the direct
* children with the given local name
* @param childLocalName the local name of children to look for
* @return the number of direct children of <code>pv</code> with the
* specified local name
*/
public static int childCount(ParameterValue pv, String childLocalName) {
if (pv == null || childLocalName == null) {
return 0;
}
int count = 0;
for (Iterator it = pv.children(); it.hasNext();) {
ParameterValue child = (ParameterValue) it.next();
if (childLocalName.equals(child.getName().getLocalPart())) {
count++;
}
}
return count;
}
public String toString() {
StringBuffer sBuf = new StringBuffer();
sBuf.append("PV [ name=");
sBuf.append(name);
if (attributes.size() > 0) {
sBuf.append(", attributes=");
sBuf.append("(");
for (Iterator it = attributes(); it.hasNext();) {
ParameterAttribute pa = (ParameterAttribute) it.next();
sBuf.append(pa.toString());
if (it.hasNext()) {
sBuf.append(", ");
}
}
sBuf.append(")");
}
if (children.size() > 0) {
sBuf.append(", children=");
sBuf.append("(");
for (Iterator it = children(); it.hasNext();) {
ParameterValue pv = (ParameterValue) it.next();
sBuf.append(pv.toString());
if (it.hasNext()) {
sBuf.append(", ");
}
}
sBuf.append(")");
}
sBuf.append(", min=");
sBuf.append(min);
sBuf.append(", max=");
sBuf.append(max);
sBuf.append(" ]");
return sBuf.toString();
}
/**
* This class allows to separate the path.
*/
protected class ParameterPath {
private static final char PATH_SEPERATOR = '/';
private static final char INDEX_BEGIN = '[';
private static final char INDEX_EMD = ']';
private String[] nodes = null;
ParameterPath(String path) {
nodes = StringUtil.split(path, PATH_SEPERATOR);
if (nodes == null) {
return;
}
}
public int getDepth() {
return (nodes == null) ? 0 : nodes.length;
}
public String getNode(int depth) {
String node = nodes[depth];
// check for index
int sPos = node.indexOf(INDEX_BEGIN);
if (sPos > -1) {
node = node.substring(0, sPos);
}
return node;
}
public int getIndex(int depth) {
String node = nodes[depth];
int index = 0;
// check for index
int sPos = node.indexOf(INDEX_BEGIN);
if (sPos > -1) {
int ePos = node.indexOf(INDEX_EMD, sPos);
index = Integer.valueOf(node.substring(sPos + 1, ePos)).intValue();
}
return index;
}
public boolean hasIndex(int depth) {
String node = nodes[depth];
// check for index
int sPos = node.indexOf(INDEX_BEGIN);
if (sPos == -1) {
return false;
}
return true;
}
public String getPath(int depth) {
String path = "";
for (int i = depth; i < nodes.length; i++) {
if (i == depth) {
path += nodes[i];
} else {
path += PATH_SEPERATOR + nodes[i];
}
}
return path;
}
}
}