/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*/
package org.enhydra.zeus.binder;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
// Zeus imports
import org.enhydra.zeus.Binder;
import org.enhydra.zeus.Binding;
import org.enhydra.zeus.Source;
import org.enhydra.zeus.ZeusDefaults;
import org.enhydra.zeus.binding.AtomicProperty;
import org.enhydra.zeus.binding.ContainerProperty;
import org.enhydra.zeus.util.NamingUtils;
import org.enhydra.zeus.util.SchemaUtils;
// JDOM imports
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
/**
* <p>
* <code>SchemaBinder</code> implements the <code>{@link Binder}</code>
* interface and allows generation of Zeus
* <code>{@link Binding}</code>s from an XML Schema.
* </p>
*
* @author Brett McLaughlin
* @version 1.0
*/
public class SchemaBinder extends BaseBinder {
/** The JDOM <code>Namespace</code> object for the schema namespace */
private Namespace schemaNamespace;
/**
* <p>
* This constructor takes in a <code>{@link Source}</code>
* to read an XML Schema from and allow generation of the
* <code>{@link Binding}</code>s from it.
* </p>
*
* @param source <code>Source</code> to read input from.
*/
public SchemaBinder(Source source) {
super(source);
// Set up default XML Schema namespace
schemaNamespace =
Namespace.getNamespace(ZeusDefaults.SCHEMA_NAMESPACE_URI);
}
/**
* <p>
* This returns the current namespace URI being used for XML Schema.
* </p>
*
* @return <code>String</code> - XML Schema namespace URI being used.
*/
public String getSchemaNamespaceURI() {
return schemaNamespace.getURI();
}
/**
* <p>
* This will set the URI to use for XML Schema. This is useful for
* documents that have an older schema namespace URI. Note that this does
* <i>not</i> imply that the <code>SchemaBinder</code> can deal with
* older schema features and syntax; this is purely for incorrect or old
* namespace URIs.
* </p>
*
* @param schemaNamespaceURI the URI to use for XML Schema
*/
public void setSchemaNamespaceURI(String schemaNamespaceURI) {
schemaNamespace = Namespace.getNamespace(schemaNamespaceURI);
}
/**
* <p>
* This is integral portion of the <code>Binder</code>. It
* is responsible for returning a Zeus representation of
* the set of constraints that this binding represents,
* resulting from the supplied XML Schema.
* </p>
*
* @return <code>List</code> - the resultant
* <code>Bindings</code> from conversion of
* constraints.
* @throws <code>IOException</code> when errors in reading
* input occur.
*/
public List getBindings() throws IOException {
Document schema = source.getDocument();
Element root = schema.getRootElement();
return convertToBindings(root);
}
/**
* <p>
* This takes a single <code>Element</code> (in JDOM form), and
* converts it to a <code>Binding</code>.
* </p>
*
* @param element <code>Element</code> to convert to a Zeus
* <code>Binding</code>.
* @return <code>List</code> - the <code>Binding</code>s from
* the supplied <code>Element</code>.
*/
private List convertToBindings(Element element) {
// Create the list to work with
List bindings = new LinkedList();
// Get all the elements named "element"
List elements = element.getChildren("element", schemaNamespace);
for (Iterator i = elements.iterator(); i.hasNext(); ) {
Element nextElement = (Element)i.next();
Attribute elementName = nextElement.getAttribute("name");
if (elementName == null) {
// All root-level elements should be named types
throw new IllegalArgumentException("All root-level elements " +
"should be named types (with a 'name' attribute).");
}
// Deal with the element definition
bindings.add(convertDefinitionToBinding(nextElement));
}
/**
* Currently, anything other than root-level elements are ignored.
* Eventually, complexTypes defined here should be handled at this
* point.
*/
// Deal with the element itself
return bindings;
}
/**
* <p>
* This will take an element that defines a structure (already determined
* by the time that this method is invoked) and convert the definition
* to a <code>{@link Binding}</code>.
* </p>
*
* @param element the element to convert
* @return <code>Binding</code> - the converted Zeus <code>Binding</code>.
*/
private Binding convertDefinitionToBinding(Element element) {
// Create base container
String xmlName = element.getAttribute("name").getValue();
String xmlType = xmlName;
ContainerProperty container =
new ContainerProperty(xmlName, xmlType);
// See if this is a simple type or complex type
Attribute type = element.getAttribute("type");
if (type != null) {
// Simple type: deal with allowed values
String schemaType = type.getValue();
// XXX: I am sure I broke this! (Sean)
AtomicProperty property =
new AtomicProperty("value", schemaType);
container.addProperty(property);
} else {
// This should be a complex type
Element complexType =
element.getChild("complexType", schemaNamespace);
if (complexType == null) {
// XXX: This should be a Zeus-specific exception
throw new IllegalArgumentException("Illegal schema: " +
"No complexType defined for this element.");
}
// Determine if this extends a base type
Attribute baseType = complexType.getAttribute("baseType");
if (baseType != null) {
// Set up the base type as a property
String schemaType = baseType.getValue();
// XXX: I am sure I broke this! (Sean)
AtomicProperty property =
new AtomicProperty("value", schemaType);
container.addProperty(property);
}
// If there is a "sequence" element, it doesn't affect anything
Element parent = complexType.getChild("sequence", schemaNamespace);
if (parent == null) {
parent = complexType;
}
// Deal with the attributes
List attributes = parent.getChildren("attribute", schemaNamespace);
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
Element theAttribute = (Element)i.next();
Attribute propertyName = theAttribute.getAttribute("name");
if (propertyName == null) {
// XXX: This should be a Zeus-specific exception
throw new IllegalArgumentException("Attributes must have " +
"a name (specified by the 'name' attribute).");
}
Attribute schemaType = theAttribute.getAttribute("type");
if (schemaType == null) {
// XXX: This should be a Zeus-specific exception
throw new IllegalArgumentException("Attributes must have " +
"a type (specified by the 'type' attribute).");
}
// XXX: I am sure I broke this! (Sean)
AtomicProperty property =
new AtomicProperty(propertyName.getValue(),
"",
"string",
ZeusDefaults.SCHEMA_NAMESPACE_URI);
container.addProperty(property);
}
// Deal with the elements
List elements = parent.getChildren("element", schemaNamespace);
for (Iterator i = elements.iterator(); i.hasNext(); ) {
Element theElement = (Element)i.next();
// If an element ref, add as a property; otherwise recurse
Attribute ref = theElement.getAttribute("ref");
if (ref == null) {
} else {
// Since this is a reference, add the property
String propertyName = ref.getValue();
String propertyType =
NamingUtils.getJavaClassName(propertyName);
// XXX: I am sure I broke this! (Sean)
AtomicProperty property
= new AtomicProperty(propertyName,
propertyType);
// Determine if a list is needed
Attribute maxOccurs = theElement.getAttribute("maxOccurs");
if (maxOccurs != null) {
String value = maxOccurs.getValue();
// The value should be either 'unbounded' or an int
if (value.equals("unbounded")) {
property.setIsCollection(true);
} else {
try {
int maxOccursInt = Integer.parseInt(value);
if (maxOccursInt > 1) {
property.setIsCollection(true);
}
} catch (NumberFormatException ignored) {
// Not a collection, so don't worry about it
}
}
}
container.addProperty(property);
}
}
}
return container;
}
}
|