| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.xmlbeans.impl.marshal; |
| |
| import org.apache.xmlbeans.Marshaller; |
| import org.apache.xmlbeans.XmlException; |
| import org.apache.xmlbeans.XmlObject; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.apache.xmlbeans.impl.binding.bts.BindingLoader; |
| import org.apache.xmlbeans.impl.binding.bts.BindingType; |
| import org.apache.xmlbeans.impl.binding.bts.BindingTypeName; |
| import org.apache.xmlbeans.impl.binding.bts.JavaTypeName; |
| import org.apache.xmlbeans.impl.binding.bts.SimpleDocumentBinding; |
| import org.apache.xmlbeans.impl.binding.bts.XmlTypeName; |
| |
| import javax.xml.namespace.NamespaceContext; |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.XMLOutputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.stream.XMLStreamWriter; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| |
| final class MarshallerImpl |
| implements Marshaller |
| { |
| //per binding context constants |
| private final BindingLoader loader; |
| private final RuntimeBindingTypeTable typeTable; |
| |
| private static final XMLOutputFactory XML_OUTPUT_FACTORY = |
| XMLOutputFactory.newInstance(); |
| |
| private static final String XML_VERSION = "1.0"; |
| |
| public MarshallerImpl(BindingLoader loader, |
| RuntimeBindingTypeTable typeTable) |
| { |
| this.loader = loader; |
| this.typeTable = typeTable; |
| } |
| |
| public XMLStreamReader marshal(Object obj, |
| NamespaceContext nscontext) |
| throws XmlException |
| { |
| final JavaTypeName jname = determineJavaType(obj); |
| final XmlTypeName elem = lookupRootElementName(jname); |
| final BindingType btype = loadBindingTypeForGlobalElem(elem, jname, obj); |
| return createMarshalResult(btype, elem.getQName(), nscontext, obj); |
| } |
| |
| private XmlTypeName lookupRootElementName(final JavaTypeName jname) |
| throws XmlException |
| { |
| final BindingTypeName root_elem_btype = loader.lookupElementFor(jname); |
| if (root_elem_btype == null) { |
| final String msg = "failed to find root " + |
| "element corresponding to " + jname; |
| throw new XmlException(msg); |
| } |
| |
| final XmlTypeName elem = root_elem_btype.getXmlName(); |
| assert elem.getComponentType() == XmlTypeName.ELEMENT; |
| return elem; |
| } |
| |
| |
| private PullMarshalResult createMarshalResult(final BindingType btype, |
| QName elem_qn, |
| NamespaceContext nscontext, |
| Object obj) |
| throws XmlException |
| { |
| assert btype != null; |
| |
| final RuntimeBindingType runtime_type = |
| typeTable.createRuntimeType(btype, loader); |
| |
| runtime_type.checkInstance(obj); |
| |
| RuntimeGlobalProperty prop = |
| new RuntimeGlobalProperty(elem_qn, runtime_type); |
| |
| return new LiteralMarshalResult(loader, typeTable, |
| nscontext, prop, obj, null); |
| } |
| |
| |
| public XMLStreamReader marshal(Object obj, |
| XmlOptions options) |
| throws XmlException |
| { |
| //TODO: actually use the options! |
| NamespaceContext nscontext = getNamespaceContextFromOptions(options); |
| return marshal(obj, nscontext); |
| } |
| |
| private static JavaTypeName determineJavaType(Object obj) |
| { |
| return determineJavaType(obj.getClass()); |
| } |
| |
| private static JavaTypeName determineJavaType(Class clazz) |
| { |
| return JavaTypeName.forClassName(clazz.getName()); |
| } |
| |
| public void marshal(XMLStreamWriter writer, Object obj) |
| throws XmlException |
| { |
| marshal(writer, obj, null); |
| } |
| |
| public void marshal(XMLStreamWriter writer, Object obj, XmlOptions options) |
| throws XmlException |
| { |
| //TODO: javadoc that pretty is not supported here. |
| |
| final JavaTypeName jname = determineJavaType(obj); |
| final XmlTypeName elem = lookupRootElementName(jname); |
| BindingType btype = loadBindingTypeForGlobalElem(elem, jname, obj); |
| |
| String encoding = getEncoding(options); |
| try { |
| if (encoding != null) { |
| writer.writeStartDocument(encoding, XML_VERSION); |
| } |
| marshalBindingType(writer, btype, obj, elem.getQName()); |
| writer.writeEndDocument(); |
| } |
| catch (XMLStreamException e) { |
| throw new XmlException(e); |
| } |
| } |
| |
| private BindingType loadBindingTypeForGlobalElem(final XmlTypeName elem, |
| final JavaTypeName jname, |
| Object obj) |
| throws XmlException |
| { |
| final XmlTypeName elem_type = |
| determineDocumentType(elem).getTypeOfElement(); |
| final BindingType btype = loadBindingType(elem_type, jname, loader); |
| if (btype == null) { |
| final String msg = "failed to find a suitable binding type for" + |
| " use in marshalling object \"" + obj + "\". " + |
| " using schema type: " + elem_type; |
| throw new XmlException(msg); |
| } |
| return btype; |
| } |
| |
| private static String getEncoding(XmlOptions options) |
| { |
| return (String)XmlOptions.safeGet(options, |
| XmlOptions.CHARACTER_ENCODING); |
| } |
| |
| public void marshal(OutputStream out, Object obj) |
| throws XmlException |
| { |
| marshal(out, obj, (XmlOptions)null); |
| } |
| |
| public void marshal(OutputStream out, Object obj, XmlOptions options) |
| throws XmlException |
| { |
| if (options != null && options.hasOption(XmlOptions.SAVE_PRETTY_PRINT)) { |
| marshalPretty(out, obj, options); |
| } else { |
| final String encoding = getEncoding(options); |
| final XMLStreamWriter writer; |
| try { |
| writer = createXmlStreamWriter(out, encoding); |
| marshal(writer, obj); |
| writer.close(); |
| } |
| catch (XMLStreamException e) { |
| throw new XmlException(e); |
| } |
| } |
| } |
| |
| |
| private void marshalPretty(OutputStream out, |
| Object obj, |
| XmlOptions options) |
| throws XmlException |
| { |
| NamespaceContext nscontext = getNamespaceContextFromOptions(options); |
| XMLStreamReader rdr = marshal(obj, nscontext); |
| XmlObject xobj = XmlObject.Factory.parse(rdr); |
| try { |
| xobj.save(out, options); |
| } |
| catch (IOException e) { |
| throw new XmlException(e); |
| } |
| } |
| |
| private static XMLStreamWriter createXmlStreamWriter(OutputStream out, |
| final String encoding) |
| throws XMLStreamException |
| { |
| final XMLStreamWriter writer; |
| if (encoding != null) { |
| writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out, encoding); |
| } else { |
| writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out); |
| } |
| return writer; |
| } |
| |
| public void marshal(OutputStream out, Object obj, String encoding) |
| throws XmlException |
| { |
| if (encoding == null) { |
| throw new IllegalArgumentException("null encoding"); |
| } |
| |
| XmlOptions opts = new XmlOptions(); |
| opts.setCharacterEncoding(encoding); |
| |
| marshal(out, obj, opts); |
| } |
| |
| |
| public XMLStreamReader marshalType(Object obj, |
| QName elementName, |
| QName schemaType, |
| String javaType, |
| NamespaceContext namespaceContext) |
| throws XmlException |
| { |
| BindingType type = lookupBindingType(schemaType, javaType, |
| elementName, obj, loader); |
| |
| return createMarshalResult(type, elementName, namespaceContext, obj); |
| } |
| |
| static BindingType lookupBindingType(QName schemaType, |
| String javaType, |
| QName elementName, |
| Object obj, |
| BindingLoader loader) |
| throws XmlException |
| { |
| return lookupBindingType(XmlTypeName.forTypeNamed(schemaType), |
| javaType, |
| elementName, obj, loader); |
| } |
| |
| private static BindingType lookupBindingType(XmlTypeName schema_type, |
| String javaType, |
| QName elementName, |
| Object obj, |
| BindingLoader loader) |
| throws XmlException |
| { |
| final BindingType type = |
| loadBindingType(schema_type, |
| JavaTypeName.forClassName(javaType), |
| loader); |
| |
| if (type == null) { |
| final String msg = "failed to find a suitable binding type for" + |
| " use in marshalling \"" + elementName + "\". " + |
| " using java type: " + javaType + |
| " schema type: " + schema_type + |
| " instance type: " + obj.getClass().getName(); |
| throw new XmlException(msg); |
| } |
| return type; |
| } |
| |
| public void marshalType(XMLStreamWriter writer, |
| Object obj, |
| QName elementName, |
| QName schemaType, |
| String javaType) |
| throws XmlException |
| { |
| final BindingType btype = lookupBindingType(schemaType, javaType, |
| elementName, obj, loader); |
| assert btype != null; |
| |
| marshalBindingType(writer, btype, obj, elementName); |
| } |
| |
| private void marshalBindingType(XMLStreamWriter writer, |
| final BindingType btype, |
| final Object obj, |
| QName elementName) |
| throws XmlException |
| { |
| final RuntimeBindingType runtime_type = |
| typeTable.createRuntimeType(btype, loader); |
| |
| runtime_type.checkInstance(obj); |
| |
| final RuntimeGlobalProperty prop = |
| new RuntimeGlobalProperty(elementName, runtime_type); |
| |
| final PushMarshalResult pmr = |
| new LiteralPushMarshalResult(loader, typeTable, writer, null); |
| |
| final RuntimeBindingType actual_rtt = |
| prop.getActualRuntimeType(obj, pmr); |
| pmr.marshalType(obj, prop, actual_rtt); |
| } |
| |
| public void marshalElement(XMLStreamWriter writer, |
| Object obj, |
| QName elementName, |
| String javaType, |
| XmlOptions options) |
| throws XmlException |
| { |
| if (writer == null) |
| throw new IllegalArgumentException("null writer"); |
| |
| final XmlTypeName elem_name = |
| XmlTypeName.forGlobalName(XmlTypeName.ELEMENT, elementName); |
| |
| final BindingType btype = |
| loadBindingTypeForGlobalElem(elem_name, |
| determineJavaType(obj), obj); |
| |
| marshalBindingType(writer, btype, obj, elementName); |
| } |
| |
| public void marshalType(XMLStreamWriter writer, |
| Object obj, |
| QName elementName, |
| QName schemaType, |
| String javaType, |
| XmlOptions options) |
| throws XmlException |
| { |
| marshalType(writer, obj, elementName, schemaType, javaType); |
| } |
| |
| public XMLStreamReader marshalType(Object obj, |
| QName elementName, |
| QName schemaType, |
| String javaType, |
| XmlOptions options) |
| throws XmlException |
| { |
| NamespaceContext nscontext = getNamespaceContextFromOptions(options); |
| |
| return marshalType(obj, elementName, schemaType, javaType, |
| nscontext); |
| } |
| |
| public XMLStreamReader marshalElement(Object obj, |
| QName elementName, |
| String javaType, |
| XmlOptions options) |
| throws XmlException |
| { |
| if (elementName == null) |
| throw new IllegalArgumentException("null elementName"); |
| |
| //TODO: we could allow javaType to be null in which case we could |
| //use our usual lookup methods (as in plain marshal) |
| if (javaType == null) |
| throw new IllegalArgumentException("null javaType"); |
| |
| |
| final SimpleDocumentBinding doc_binding = |
| determineDocumentType(elementName); |
| |
| final XmlTypeName elem_type = doc_binding.getTypeOfElement(); |
| |
| final BindingType type = |
| lookupBindingType(elem_type, javaType, elementName, obj, loader); |
| |
| NamespaceContext nscontext = getNamespaceContextFromOptions(options); |
| return createMarshalResult(type, elementName, |
| nscontext, obj); |
| } |
| |
| static NamespaceContext getNamespaceContextFromOptions(XmlOptions options) |
| { |
| //TODO: do this properly |
| return EmptyNamespaceContext.getInstance(); |
| } |
| |
| private SimpleDocumentBinding determineDocumentType(QName global_element) |
| throws XmlException |
| { |
| final XmlTypeName type_name = |
| XmlTypeName.forGlobalName(XmlTypeName.ELEMENT, global_element); |
| return determineDocumentType(type_name); |
| } |
| |
| private SimpleDocumentBinding determineDocumentType(XmlTypeName global_element) |
| throws XmlException |
| { |
| BindingType doc_binding_type = getPojoBindingType(global_element); |
| assert doc_binding_type != null; |
| return (SimpleDocumentBinding)doc_binding_type; |
| } |
| |
| |
| private BindingType getPojoBindingType(final XmlTypeName type_name) |
| throws XmlException |
| { |
| final BindingTypeName btName = loader.lookupPojoFor(type_name); |
| if (btName == null) { |
| final String msg = "failed to load java type corresponding " + |
| "to " + type_name; |
| throw new XmlException(msg); |
| } |
| |
| BindingType bt = loader.getBindingType(btName); |
| |
| if (bt == null) { |
| final String msg = "failed to load BindingType for " + btName; |
| throw new XmlException(msg); |
| } |
| |
| assert bt != null; |
| return bt; |
| } |
| |
| |
| |
| |
| //TODO: refine this algorithm to deal better |
| //with primitives/interfaces/other oddities |
| //we are basically just walking up the super types |
| //till we hit a class that we can deal with. |
| |
| //returns null if we fail |
| static BindingType lookupBindingType(Class instance_type, |
| JavaTypeName java_type, |
| XmlTypeName xml_type, |
| BindingLoader loader) |
| throws XmlException |
| { |
| //look first for exact match |
| { |
| JavaTypeName jname = determineJavaType(instance_type); |
| BindingType bt = loadBindingType(xml_type, jname, loader); |
| if (bt != null) return bt; //success! |
| } |
| |
| |
| BindingType binding_type = null; |
| Class curr_class = instance_type; |
| Class super_type = null; |
| |
| while (true) { |
| JavaTypeName jname = determineJavaType(curr_class); |
| |
| BindingTypeName btype_name = loader.lookupTypeFor(jname); |
| if (btype_name != null) { |
| binding_type = loader.getBindingType(btype_name); |
| if (binding_type == null) { |
| String e = "binding configuration inconsistency: found " + |
| btype_name + " defined for " + jname + " but failed " + |
| "to load the type"; |
| throw new XmlException(e); |
| } else { |
| return binding_type; //success! |
| } |
| } |
| |
| super_type = curr_class.getSuperclass(); |
| |
| //note that we check that this super-super type check is to avoid |
| //getting a match on java.lang.Object, which doesn't do us any good |
| if (super_type == null || (super_type.getSuperclass() == null)) { |
| break; |
| } |
| |
| curr_class = super_type; |
| } |
| |
| //reaching here means that we've failed using the actual instance, |
| //so let's try the expected type |
| assert (binding_type == null); |
| return loadBindingType(xml_type, java_type, loader); |
| } |
| |
| |
| private static BindingType loadBindingType(XmlTypeName xname, |
| JavaTypeName jname, |
| BindingLoader loader) |
| { |
| BindingTypeName btname = BindingTypeName.forPair(jname, xname); |
| return loader.getBindingType(btname); |
| } |
| |
| } |