/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Emil Ong, Adam Megacz */ package com.caucho.jaxb; import com.caucho.jaxb.skeleton.*; import com.caucho.jaxb.property.*; import com.caucho.server.util.CauchoSystem; import com.caucho.util.L10N; import com.caucho.xml.QNode; import org.w3c.dom.Node; import javax.activation.DataHandler; import javax.xml.bind.*; import javax.xml.bind.annotation.*; import javax.xml.datatype.*; import javax.xml.namespace.QName; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; import javax.xml.transform.Result; import java.awt.Image; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.util.*; /** * Entry point to API */ public class JAXBContextImpl extends JAXBContext { public static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; public static final String TARGET_NAMESPACE = "com.caucho.jaxb.targetNamespace"; static final ValidationEventHandler DEFAULT_VALIDATION_EVENT_HANDLER = new DefaultValidationEventHandler(); private static final L10N L = new L10N(JAXBContextImpl.class); private static final HashSet<Class> _specialClasses = new HashSet<Class>(); static { _specialClasses.add(Object.class); _specialClasses.add(Class.class); _specialClasses.add(String.class); _specialClasses.add(Double.class); _specialClasses.add(Float.class); _specialClasses.add(Integer.class); _specialClasses.add(Long.class); _specialClasses.add(Boolean.class); _specialClasses.add(Character.class); _specialClasses.add(Short.class); _specialClasses.add(Byte.class); _specialClasses.add(BigDecimal.class); _specialClasses.add(BigInteger.class); _specialClasses.add(QName.class); _specialClasses.add(Date.class); _specialClasses.add(Calendar.class); _specialClasses.add(XMLGregorianCalendar.class); _specialClasses.add(UUID.class); _specialClasses.add(DataHandler.class); } private String _targetNamespace = null; private String[] _packages; private JAXBIntrospector _jaxbIntrospector; private XMLInputFactory _staxInputFactory; private XMLOutputFactory _staxOutputFactory; private final ArrayList<ObjectFactorySkeleton> _objectFactories = new ArrayList<ObjectFactorySkeleton>(); private final HashMap<String,Object> _properties = new HashMap<String,Object>(); private final LinkedHashMap<Class,ClassSkeleton> _classSkeletons = new LinkedHashMap<Class,ClassSkeleton>(); private final LinkedHashMap<Class,JAXBElementSkeleton> _jaxbElementSkeletons = new LinkedHashMap<Class,JAXBElementSkeleton>(); private final HashMap<QName,ClassSkeleton> _roots = new HashMap<QName,ClassSkeleton>(); private final HashMap<QName,ClassSkeleton> _types = new HashMap<QName,ClassSkeleton>(); private final ArrayList<EnumProperty> _enums = new ArrayList<EnumProperty>(); private HashSet<Class> _pendingSkeletons = new HashSet<Class>(); private DynamicJAXBElementSkeleton _dynamicSkeleton; private Property _laxAnyTypeProperty = null; private boolean _isDiscoveryFinished = false; public static JAXBContext createContext(String contextPath, ClassLoader classLoader, Map<String,?> properties) throws JAXBException { return new JAXBContextImpl(contextPath, classLoader, properties); } public JAXBContextImpl(String contextPath, ClassLoader classLoader, Map<String,?> properties) throws JAXBException { _jaxbIntrospector = new JAXBIntrospectorImpl(this); StringTokenizer st = new StringTokenizer(contextPath, ":"); if (properties != null) _targetNamespace = (String) properties.get(TARGET_NAMESPACE); do { String packageName = st.nextToken(); loadPackage(packageName, classLoader); } while (st.hasMoreTokens()); init(properties); } public static JAXBContext createContext(Class []classes, Map<String,?> properties) throws JAXBException { return new JAXBContextImpl(classes, properties); } public JAXBContextImpl(Class[] classes, Map<String,?> properties) throws JAXBException { _jaxbIntrospector = new JAXBIntrospectorImpl(this); _packages = new String[0]; if (properties != null) _targetNamespace = (String) properties.get(TARGET_NAMESPACE); for(Class c : classes) { if (c.getName().endsWith(".ObjectFactory")) introspectObjectFactory(c); else if (! c.isPrimitive() && // XXX pull out to JAX-WS? ! c.isArray() && ! _specialClasses.contains(c)) createSkeleton(c); } init(properties); } private void init(Map<String,?> properties) throws JAXBException { if (properties != null) { for(Map.Entry<String,?> e : properties.entrySet()) setProperty(e.getKey(), e.getValue()); } DatatypeConverter.setDatatypeConverter(new DatatypeConverterImpl()); _dynamicSkeleton = new DynamicJAXBElementSkeleton(this); _isDiscoveryFinished = true; for (ClassSkeleton skeleton : _classSkeletons.values()) skeleton.postProcess(); } public boolean isDiscoveryFinished() { return _isDiscoveryFinished; } public Marshaller createMarshaller() throws JAXBException { return new MarshallerImpl(this); } public Unmarshaller createUnmarshaller() throws JAXBException { return new UnmarshallerImpl(this); } public Validator createValidator() throws JAXBException { throw new UnsupportedOperationException(); } XMLStreamReader getXMLStreamReader(InputStream is) throws XMLStreamException { if (_staxInputFactory == null) _staxInputFactory = XMLInputFactory.newInstance(); return _staxInputFactory.createXMLStreamReader(is); } public XMLInputFactory getXMLInputFactory() { if (_staxInputFactory == null) _staxInputFactory = XMLInputFactory.newInstance(); return _staxInputFactory; } public XMLOutputFactory getXMLOutputFactory() { if (_staxOutputFactory == null) { _staxOutputFactory = XMLOutputFactory.newInstance(); _staxOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE); } return _staxOutputFactory; } public String getTargetNamespace() { return _targetNamespace; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("JAXBContext["); for (Class c : _classSkeletons.keySet()) sb.append(c.getName() + ":"); for (int i = 0; i < _packages.length; i++) { String p = _packages[i]; sb.append(p + (i < _packages.length - 1 ? ":" : "")); } sb.append("]"); return sb.toString(); } private void setProperty(String key, Object val) { _properties.put(key, val); } public Binder<Node> createBinder() { return (Binder<Node>) new BinderImpl(this); } public <T> Binder<T> createBinder(Class<T> domType) { if (! domType.equals(QNode.class)) throw new UnsupportedOperationException("Unsupported implementation: " + domType); return (Binder) new BinderImpl(this); } public JAXBIntrospector createJAXBIntrospector() { return _jaxbIntrospector; } public void generateSchema(SchemaOutputResolver outputResolver) throws IOException { Result result = outputResolver.createOutput("", "schema1.xsd"); XMLStreamWriter out = null; try { XMLOutputFactory factory = getXMLOutputFactory(); out = factory.createXMLStreamWriter(result); out.writeStartDocument("UTF-8", "1.0"); out.writeStartElement("xsd", "schema", XML_SCHEMA_NS); out.writeAttribute("version", "1.0"); generateSchemaWithoutHeader(out); out.writeEndElement(); // schema } catch (Exception e) { IOException ioException = new IOException(); ioException.initCause(e); throw ioException; } finally { try { out.close(); } catch (XMLStreamException e) { throw new IOException(e.toString()); } } } public void generateSchemaWithoutHeader(XMLStreamWriter out) throws JAXBException, XMLStreamException { for (ClassSkeleton skeleton : _classSkeletons.values()) skeleton.generateSchema(out); for (int i = 0; i < _enums.size(); i++) ((EnumProperty) _enums.get(i)).generateSchema(out); } public ClassSkeleton createSkeleton(Class c) throws JAXBException { ClassSkeleton skeleton = _classSkeletons.get(c); if (skeleton != null) return skeleton; if (Object.class.equals(c)) { skeleton = new AnyTypeSkeleton(this); _classSkeletons.put(c, skeleton); } else { // XXX if (c.isEnum() || c.isInterface()) { return null; /* throw new IllegalStateException(L.l("{0}: Can't create skeleton for an interface or enum", c.getName())); */ } skeleton = new ClassSkeleton(this, c); // Breadcrumb to prevent problems with recursion _classSkeletons.put(c, skeleton); _pendingSkeletons.add(c); skeleton.init(); _pendingSkeletons.remove(c); } return skeleton; } public ClassSkeleton getSkeleton(Class c) throws JAXBException { return createSkeleton(c); } public boolean hasSkeleton(Class c) { return _classSkeletons.containsKey(c); } public ClassSkeleton findSkeletonForClass(Class cl) throws JAXBException { return findSkeletonForClass(cl, _jaxbElementSkeletons); } public ClassSkeleton findSkeletonForClass(Class cl, Map<Class,? extends ClassSkeleton> map) { Class givenClass = cl; while (! cl.equals(Object.class)) { ClassSkeleton skeleton = map.get(cl); if (skeleton != null) return skeleton; cl = cl.getSuperclass(); } return null; } public ClassSkeleton findSkeletonForObject(Object obj) { if (obj instanceof JAXBElement) { JAXBElement element = (JAXBElement) obj; obj = element.getValue(); ClassSkeleton skeleton = findSkeletonForClass(obj.getClass(), _jaxbElementSkeletons); if (skeleton == null) skeleton = _dynamicSkeleton; return skeleton; } else return findSkeletonForClass(obj.getClass(), _classSkeletons); } /** * Finds all ClassSkeletons that are subclasses of the given class and * are root elements. **/ public List<ClassSkeleton> getRootElements(Class cl) { ArrayList<ClassSkeleton> list = new ArrayList<ClassSkeleton>(); for (Map.Entry<Class,ClassSkeleton> entry : _classSkeletons.entrySet()) { if (cl.isAssignableFrom(entry.getKey()) && entry.getValue().isRootElement()) list.add(entry.getValue()); } return list; } public Property getLaxAnyTypeProperty() throws JAXBException { if (_laxAnyTypeProperty == null) _laxAnyTypeProperty = new LaxAnyTypeProperty(this); return _laxAnyTypeProperty; } // XXX clean up all this argument tiering public Property createProperty(Type type) throws JAXBException { return createProperty(type, false); } public Property createProperty(Type type, boolean anyType) throws JAXBException { return createProperty(type, anyType, null); } public Property createProperty(Type type, boolean anyType, String mimeType) throws JAXBException { return createProperty(type, anyType, mimeType, false); } public Property createProperty(Type type, boolean anyType, String mimeType, boolean xmlList) throws JAXBException { return createProperty(type, anyType, mimeType, xmlList, false); } public Property createProperty(Type type, boolean anyType, String mimeType, boolean xmlList, boolean xmlValue) throws JAXBException { if (type instanceof Class) { if (anyType && Object.class.equals(type)) return getLaxAnyTypeProperty(); Property simpleTypeProperty = getSimpleTypeProperty((Class) type, mimeType); if (simpleTypeProperty != null) { // jaxb/12gb if (simpleTypeProperty == ByteArrayProperty.PROPERTY && xmlList && ! xmlValue) throw new JAXBException(L.l("@XmlList applied to byte[] valued fields or properties")); return simpleTypeProperty; } Class cl = (Class) type; if (cl.isArray()) { Property componentProperty = createProperty(cl.getComponentType(), anyType); if (xmlList) { if (! (componentProperty instanceof CDataProperty)) throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types")); Class componentType = cl.getComponentType(); CDataProperty cdataProperty = (CDataProperty) componentProperty; return XmlListArrayProperty.createXmlListArrayProperty(cdataProperty, componentType); } else return ArrayProperty.createArrayProperty(componentProperty, cl.getComponentType()); } if (cl.isEnum()) { EnumProperty enumProperty = new EnumProperty(cl, this); _enums.add(enumProperty); return enumProperty; } // XXX Map if (List.class.isAssignableFrom(cl)) { Property property = new SkeletonProperty(getSkeleton(Object.class)); if (xmlList) { throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types")); } else return new ListProperty(property); } if (Collection.class.isAssignableFrom(cl)) { Property property = new SkeletonProperty(getSkeleton(Object.class)); if (xmlList) { throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types")); } else return new CollectionProperty(property); } ClassSkeleton skel = getSkeleton(cl); // XXX: interfaces if (skel != null) return new SkeletonProperty(skel); else return null; } else if (type instanceof ParameterizedType) { ParameterizedType ptype = (ParameterizedType) type; Type rawType = ptype.getRawType(); if (rawType instanceof Class) { Class rawClass = (Class) rawType; if (Map.class.isAssignableFrom(rawClass)) { Type[] args = ptype.getActualTypeArguments(); if (args.length != 2) throw new JAXBException(L.l("unexpected number of generic arguments for Map<>: {0}", args.length)); Property keyProperty = createProperty(args[0], anyType); Property valueProperty = createProperty(args[1], anyType); return new MapProperty(rawClass, keyProperty, valueProperty); } if (Collection.class.isAssignableFrom(rawClass)) { Type[] args = ptype.getActualTypeArguments(); if (args.length != 1) throw new JAXBException(L.l("unexpected number of generic arguments for List<>: {0}", args.length)); Property componentProperty = createProperty(args[0], anyType); if (xmlList) { if (! (componentProperty instanceof CDataProperty)) throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types")); CDataProperty cdataProperty = (CDataProperty) componentProperty; return new XmlListCollectionProperty(cdataProperty, rawClass); } else if (List.class.isAssignableFrom(rawClass)) return new ListProperty(componentProperty); else return new CollectionProperty(componentProperty); } } } else if (type instanceof GenericArrayType) { Type component = ((GenericArrayType) type).getGenericComponentType(); if (byte.class.equals(component)) return ByteArrayProperty.PROPERTY; // XXX other component types? } throw new JAXBException(L.l("Unrecognized type: {0}", type.toString())); } public Property getSimpleTypeProperty(Class type) throws JAXBException { return getSimpleTypeProperty(type, null); } public Property getSimpleTypeProperty(Class type, String mimeType) throws JAXBException { if (String.class.equals(type)) return StringProperty.PROPERTY; if (URI.class.equals(type)) return URIProperty.PROPERTY; if (UUID.class.equals(type)) return UUIDProperty.PROPERTY; if (Double.class.equals(type)) return DoubleProperty.OBJECT_PROPERTY; if (Double.TYPE.equals(type)) return DoubleProperty.PRIMITIVE_PROPERTY; if (Float.class.equals(type)) return FloatProperty.OBJECT_PROPERTY; if (Float.TYPE.equals(type)) return FloatProperty.PRIMITIVE_PROPERTY; if (Integer.class.equals(type)) return IntProperty.OBJECT_PROPERTY; if (Integer.TYPE.equals(type)) return IntProperty.PRIMITIVE_PROPERTY; if (Long.class.equals(type)) return LongProperty.OBJECT_PROPERTY; if (Long.TYPE.equals(type)) return LongProperty.PRIMITIVE_PROPERTY; if (Boolean.class.equals(type)) return BooleanProperty.OBJECT_PROPERTY; if (Boolean.TYPE.equals(type)) return BooleanProperty.PRIMITIVE_PROPERTY; if (Character.class.equals(type)) return CharacterProperty.OBJECT_PROPERTY; if (Character.TYPE.equals(type)) return CharacterProperty.PRIMITIVE_PROPERTY; if (Short.class.equals(type)) return ShortProperty.OBJECT_PROPERTY; if (Short.TYPE.equals(type)) return ShortProperty.PRIMITIVE_PROPERTY; if (Byte.class.equals(type)) return ByteProperty.OBJECT_PROPERTY; if (Byte.TYPE.equals(type)) return ByteProperty.PRIMITIVE_PROPERTY; if (BigDecimal.class.equals(type)) return BigDecimalProperty.PROPERTY; if (BigInteger.class.equals(type)) return BigIntegerProperty.PROPERTY; if (QName.class.equals(type)) return QNameProperty.PROPERTY; if (Date.class.equals(type)) return DateTimeProperty.PROPERTY; if (Calendar.class.equals(type)) return CalendarProperty.PROPERTY; if (Duration.class.equals(type)) return DurationProperty.PROPERTY; if (XMLGregorianCalendar.class.equals(type)) return XMLGregorianCalendarProperty.PROPERTY; if (Image.class.equals(type)) return ImageProperty.getImageProperty(mimeType); if (DataHandler.class.equals(type)) return DataHandlerProperty.PROPERTY; if (Source.class.equals(type)) return SourceProperty.PROPERTY; if (byte[].class.equals(type)) return ByteArrayProperty.PROPERTY; return null; } public void addXmlType(QName typeName, ClassSkeleton skeleton) throws JAXBException { if (_types.containsKey(typeName)) { ClassSkeleton existing = _types.get(typeName); if (! _pendingSkeletons.contains(existing.getType())) { throw new JAXBException(L.l("Duplicate type name {0} for types {1} and {2}", typeName, skeleton.getType(), existing.getType())); } } _types.put(typeName, skeleton); } public void addRootElement(ClassSkeleton s) throws JAXBException { ClassSkeleton old = _roots.get(s.getElementName()); // Use != here to check duplicate puts; equals() isn't necessary if (old != null && old != s && ! _pendingSkeletons.contains(s.getType())) { throw new JAXBException(L.l("Duplicate name {0} for classes {1} and {2}", s.getElementName(), s.getType(), _roots.get(s.getElementName()).getType())); } _roots.put(s.getElementName(), s); } public boolean hasXmlType(QName typeName) { return _types.containsKey(typeName); } public boolean hasRootElement(QName elementName) { return _roots.containsKey(elementName); } public ClassSkeleton getRootElement(QName q) { return _roots.get(q); } private void loadPackage(String packageName, ClassLoader classLoader) throws JAXBException { boolean success = false; try { Class cl = Class.forName(packageName + ".ObjectFactory"); introspectObjectFactory(cl); success = true; } catch (ClassNotFoundException e) { // we can still try for jaxb.index } String resourceName = packageName.replace('.', '/') + "/jaxb.index"; // For some reason, this approach works when running resin... InputStream is = this.getClass().getResourceAsStream('/' + resourceName); // ...and this approach works in QA if (is == null) is = classLoader.getResourceAsStream(resourceName); if (is == null) { if (success) return; throw new JAXBException(L.l("Unable to open jaxb.index for package {0}", packageName)); } try { InputStreamReader isr = new InputStreamReader(is, "utf-8"); LineNumberReader in = new LineNumberReader(isr); for (String line = in.readLine(); line != null; line = in.readLine()) { String[] parts = line.split("#", 2); String className = parts[0].trim(); if (! "".equals(className)) { Class cl = classLoader.loadClass(packageName + "." + className); createSkeleton(cl); } } } catch (IOException e) { throw new JAXBException(L.l("Error while reading jaxb.index for package {0}", packageName), e); } catch (ClassNotFoundException e) { throw new JAXBException(e); } } private void introspectObjectFactory(Class factoryClass) throws JAXBException { Object objectFactory = null; try { objectFactory = factoryClass.newInstance(); } catch (Exception e) { throw new JAXBException(e); } String namespace = null; Package pkg = factoryClass.getPackage(); if (pkg.isAnnotationPresent(XmlSchema.class)) { XmlSchema schema = (XmlSchema) pkg.getAnnotation(XmlSchema.class); if (! "".equals(schema.namespace())) namespace = schema.namespace(); } Method[] methods = factoryClass.getMethods(); for (Method method : methods) { if (method.getName().startsWith("create")) { XmlElementDecl decl = method.getAnnotation(XmlElementDecl.class); Class cl = method.getReturnType(); ClassSkeleton skeleton = null; QName root = null; if (cl.equals(JAXBElement.class)) { ParameterizedType type = (ParameterizedType) method.getGenericReturnType(); cl = (Class) type.getActualTypeArguments()[0]; skeleton = new JAXBElementSkeleton(this, cl, method, objectFactory); _jaxbElementSkeletons.put(cl, (JAXBElementSkeleton) skeleton); } else { skeleton = getSkeleton(cl); if (skeleton == null) skeleton = createSkeleton(cl); skeleton.setCreateMethod(method, objectFactory); root = skeleton.getElementName(); } if (decl != null) { String localName = decl.name(); if (! "##default".equals(decl.namespace())) namespace = decl.namespace(); if (namespace == null) root = new QName(localName); else root = new QName(namespace, localName); } skeleton.setElementName(root); addRootElement(skeleton); } else if (method.getName().equals("newInstance")) { // XXX } else if (method.getName().equals("getProperty")) { // XXX } else if (method.getName().equals("setProperty")) { // XXX } } } static class DefaultValidationEventHandler implements ValidationEventHandler { public boolean handleEvent(ValidationEvent event) { if (event == null) throw new IllegalArgumentException("Event may not be null"); return event.getSeverity() != ValidationEvent.FATAL_ERROR; } } }