001: package org.obe.util;
002:
003: import org.apache.commons.logging.Log;
004: import org.apache.commons.logging.LogFactory;
005: import org.apache.xmlbeans.*;
006: import org.obe.XMLException;
007: import org.w3c.dom.Document;
008: import org.w3c.dom.Element;
009: import org.w3c.dom.NamedNodeMap;
010: import org.w3c.dom.Node;
011: import org.xml.sax.EntityResolver;
012: import org.xml.sax.InputSource;
013: import org.xml.sax.helpers.DefaultHandler;
014:
015: import javax.xml.namespace.QName;
016: import javax.xml.transform.Source;
017: import javax.xml.transform.TransformerFactory;
018: import javax.xml.transform.URIResolver;
019: import javax.xml.transform.stream.StreamSource;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.Reader;
023: import java.math.BigDecimal;
024: import java.math.BigInteger;
025: import java.util.*;
026:
027: /**
028: * Utilities for mapping XML Schema constructs to XPDL constructs.
029: *
030: * @author Adrian Price
031: */
032: public final class SchemaUtils {
033: private static final Log _logger = LogFactory
034: .getLog(SchemaUtils.class);
035: // Maps XML Schema types to Java classes.
036: // N.B. These mappings preserve precision, and create as exact a Java
037: // representation as possible. However, this is sometimes at the expense of
038: // relational query-ability. For example, in order to preserve the precision
039: // of xsd:decimal, it is mapped to java.lang.BigDecimal, which is currently
040: // stored in the OBEATTRIBUTEINSTANCE.OBJVALUE column, which is a BLOB and
041: // thus non-queryable. Possible solutions to this problem could include:
042: // - adding more columns to the OBEATTRIBUTEINSTANCE table.
043: // Ultimately we're mapping to JDBC types anyway (discounting non-persistent
044: // and serialized Java object instance repository implementations).
045: private static final Object[][] _xsdToJava = {
046: { XmlAnySimpleType.type, Object.class },
047: { XmlDuration.type, String.class },
048: { XmlDateTime.type, Date.class }, // JAX-RPC mapping: (Calendar.class)
049: { XmlTime.type, Date.class }, // JAX-RPC mapping: (Calendar.class)
050: { XmlDate.type, Date.class }, // JAX-RPC mapping: (Calendar.class)
051: { XmlGYearMonth.type, String.class },
052: { XmlGYear.type, String.class },
053: { XmlGMonthDay.type, String.class },
054: { XmlGDay.type, String.class },
055: { XmlGMonth.type, String.class },
056: { XmlBoolean.type, boolean.class },
057: { XmlBase64Binary.type, byte[].class },
058: { XmlHexBinary.type, byte[].class },
059: { XmlFloat.type, Float.class },
060: { XmlDouble.type, Double.class },
061: { XmlAnyURI.type, String.class },
062: { XmlQName.type, QName.class }, { XmlNOTATION.type, null },
063: { XmlString.type, String.class },
064: { XmlNormalizedString.type, String.class },
065: { XmlToken.type, String.class },
066: { XmlLanguage.type, String.class },
067: { XmlName.type, String.class },
068: { XmlNCName.type, String.class },
069: { XmlID.type, String.class },
070: { XmlIDREF.type, String.class },
071: { XmlIDREFS.type, String.class },
072: { XmlENTITY.type, String.class },
073: { XmlENTITIES.type, String.class },
074: { XmlNMTOKEN.type, String.class },
075: { XmlNMTOKENS.type, String.class },
076: { XmlDecimal.type, BigDecimal.class },
077: { XmlInteger.type, BigInteger.class },
078: { XmlNonPositiveInteger.type, BigInteger.class },
079: { XmlNegativeInteger.type, BigInteger.class },
080: { XmlLong.type, Long.class },
081: { XmlInt.type, Integer.class },
082: { XmlShort.type, Short.class },
083: { XmlByte.type, Byte.class },
084: { XmlNonNegativeInteger.type, BigInteger.class },
085: { XmlPositiveInteger.type, BigInteger.class },
086: { XmlUnsignedLong.type, Long.class },
087: { XmlUnsignedInt.type, Long.class },
088: { XmlUnsignedShort.type, Short.class },
089: { XmlUnsignedByte.type, Short.class }, };
090: private static final Map _xsdToJavaMap = new HashMap();
091: private static EntityResolver _entityResolver;
092: private static URIResolver _uriResolver;
093:
094: static {
095: for (int i = 0; i < _xsdToJava.length; i++)
096: _xsdToJavaMap.put(_xsdToJava[i][0], _xsdToJava[i][1]);
097: }
098:
099: public static Class classForSchemaType(Element schemaElem,
100: QName name) throws XMLException {
101:
102: try {
103: if (schemaElem != null) {
104: // First, note namespaces prefixes defined on the schema node.
105: Set nsPrefixes = new HashSet();
106: NamedNodeMap attrs = schemaElem.getAttributes();
107: for (int i = 0, n = attrs.getLength(); i < n; i++) {
108: Node attr = attrs.item(i);
109: if (W3CNames.XMLNS_NS_PREFIX.equals(attr
110: .getPrefix()))
111: nsPrefixes.add(attr.getLocalName());
112: }
113:
114: // Duplicate any extra accessible namespaces declared by
115: // parents, because XMLBeans doesn't recurse above the schema
116: // node when looking up namespaces.
117: Node parent = schemaElem;
118: while ((parent = parent.getParentNode()) instanceof Element) {
119: attrs = parent.getAttributes();
120: for (int i = 0, n = attrs.getLength(); i < n; i++) {
121: Node attr = attrs.item(i);
122: String nsPrefix = attr.getPrefix();
123: String localName = attr.getLocalName();
124:
125: // If this is a namespace declaration that isn't already
126: // declared by the schema node, duplicate and add it.
127: if (W3CNames.XMLNS_NS_PREFIX.equals(nsPrefix)
128: && !nsPrefixes.contains(localName)) {
129:
130: schemaElem.setAttributeNS(
131: W3CNames.XMLNS_NS_URI,
132: W3CNames.XMLNS_NS_PREFIX + ':'
133: + localName, attr
134: .getNodeValue());
135: nsPrefixes.add(localName);
136: }
137: }
138: }
139: }
140:
141: // Parse the schema.
142: return classForSchemaType(schemaElem == null ? null
143: : XmlObject.Factory.parse(schemaElem), name);
144: } catch (XmlException e) {
145: throw new XMLException(e);
146: }
147: }
148:
149: public static Class classForSchemaType(InputStream in, QName name)
150: throws XMLException {
151:
152: try {
153: return classForSchemaType(in == null ? null
154: : XmlObject.Factory.parse(in), name);
155: } catch (IOException e) {
156: throw new XMLException(e);
157: } catch (XmlException e) {
158: throw new XMLException(e);
159: }
160: }
161:
162: public static Class classForSchemaType(Reader in, QName name)
163: throws XMLException {
164:
165: try {
166: return classForSchemaType(in == null ? null
167: : XmlObject.Factory.parse(in), name);
168: } catch (IOException e) {
169: throw new XMLException(e);
170: } catch (XmlException e) {
171: throw new XMLException(e);
172: }
173: }
174:
175: private static Class classForSchemaType(XmlObject xmlObject,
176: QName name) throws XmlException {
177:
178: // Schema defined data types will be stored as DOM/XML documents unless
179: // the schema type is simple and maps to a Java class.
180: // N.B. Wondering about this... if we force XML data to be
181: // represented as a DOM document, we won't be able to store or
182: // manipulate it using any other type mapping system. For now, let's
183: // see how far we can get by relying on JAX-RPC-style type mappings.
184: // Serializable JavaBeans are a much more efficient way to store data
185: // from XML documents than DOM Documents or XML strings.
186: Class javaClass = /*Object*/Document.class;
187:
188: // If supplied, assemble the parsed schema document into an XSD types
189: // system. Otherwise, just use the built-in XML Schema types system.
190: SchemaTypeSystem bits = XmlBeans.getBuiltinTypeSystem();
191: SchemaTypeSystem schemaTypeSystem = xmlObject == null ? bits
192: : XmlBeans.compileXsd(new XmlObject[] { xmlObject },
193: bits, null);
194:
195: // If the schema defines any global types, either accept the first such
196: // type or search for the requested type.
197: SchemaType[] schemaTypes = schemaTypeSystem.globalTypes();
198: if (schemaTypes.length > 0) {
199: for (int i = 0; i < schemaTypes.length; i++) {
200: SchemaType schemaType = schemaTypes[i];
201: if (name == null || name.equals(schemaType.getName())) {
202: javaClass = Object.class;
203:
204: // Determine the primitive type that underlies this type.
205: SchemaType baseType = schemaType;
206: while (true) {
207: int variety = baseType.getSimpleVariety();
208: switch (variety) {
209: case SchemaType.ATOMIC:
210: baseType = schemaType.getPrimitiveType();
211: break;
212: case SchemaType.LIST:
213: javaClass = List.class;
214: break;
215: case SchemaType.UNION:
216: baseType = schemaType
217: .getUnionCommonBaseType();
218: continue;
219: default:
220: break;
221: }
222: break;
223: }
224:
225: // Map a primitive schema type to a Java class.
226: if (baseType.isPrimitiveType())
227: javaClass = (Class) _xsdToJavaMap.get(baseType);
228:
229: // We've identified the Java type, break out of the loop.
230: break;
231: }
232: }
233: }
234:
235: return javaClass;
236: }
237:
238: public static synchronized EntityResolver getEntityResolver() {
239: if (_entityResolver == null) {
240: _entityResolver = new DefaultHandler();
241: _logger.warn("Default EntityResolver created");
242: }
243: return _entityResolver;
244: }
245:
246: public static synchronized void setEntityResolver(
247: EntityResolver entityResolver) {
248:
249: if (_entityResolver != null && entityResolver != null)
250: _logger.warn("EntityResolver already set - ignoring call");
251: else
252: _entityResolver = entityResolver;
253: }
254:
255: public static synchronized URIResolver getURIResolver() {
256: if (_uriResolver == null) {
257: _uriResolver = TransformerFactory.newInstance()
258: .getURIResolver();
259: _logger.warn("Default URIResolver created");
260: }
261: return _uriResolver;
262: }
263:
264: public static synchronized void setURIResolver(
265: URIResolver uriResolver) {
266: if (_uriResolver != null && uriResolver != null)
267: _logger.warn("URIResolver already set - ignoring call");
268: else
269: _uriResolver = uriResolver;
270: }
271:
272: public static void close(InputSource in) throws IOException {
273: if (in != null) {
274: InputStream is = in.getByteStream();
275: if (is != null)
276: is.close();
277: Reader rdr = in.getCharacterStream();
278: if (rdr != null)
279: rdr.close();
280: }
281: }
282:
283: public static void close(Source src) throws IOException {
284: if (src instanceof StreamSource) {
285: StreamSource ss = (StreamSource) src;
286: InputStream in = ss.getInputStream();
287: if (in != null)
288: in.close();
289: Reader rdr = ss.getReader();
290: if (rdr != null)
291: rdr.close();
292: }
293: }
294:
295: private SchemaUtils() {
296: }
297: }
|