blob: e8ee76fac197ecf9819b607def3b347b48dd6986 [file] [log] [blame]
/* 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);
}
}