Java tutorial
/* * Copyright 2005-2012 the original author or authors. * * 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.javelin.sws.ext.bind; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.Validator; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; import javax.xml.stream.events.XMLEvent; import org.apache.ws.commons.schema.XmlSchemaType; import org.javelin.sws.ext.bind.internal.BuiltInMappings; import org.javelin.sws.ext.bind.internal.metadata.JaxbMetadata; import org.javelin.sws.ext.bind.internal.metadata.PropertyCallback; import org.javelin.sws.ext.bind.internal.model.ElementPattern; import org.javelin.sws.ext.bind.internal.model.TemporaryTypedPattern; import org.javelin.sws.ext.bind.internal.model.TypedPattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; /** * <p>Lightweight implementation of {@link JAXBContext}. Almost everything what JAXB-RI does is implemented using Spring features * (metadata scanning, handling of JavaBeans, conversion, formatting, etc.)</p> * * <p>Construction of {@link JAXBContext} is the process of <i>lifting up</i> (in Category theory terms using <i>functor</i>) the Category * of Java classes into the Category of what we call <i>XML Patterns</i>. In Java Category, Java classes are transformed (morphed) into Java objects; * in <i>XML Patterns</i> Category, we morph between XML Patterns and Java objects. Of course this analogy needs more clarification!</p> * * <p>The problem with {@link JAXBContext} is that it also provides the method {@link JAXBContext#generateSchema(javax.xml.bind.SchemaOutputResolver)} * which is <b>not</b> an OXM functionality! This single method brings second (ang huge!) responsibility to {@link JAXBContext}. It is no longer * a recipe for changing Java objects into XML and vice versa. The <i>Patterns</i> need also be aware of the meta-information on the XML * fragments they produce/consume. We'll use what's best for this - Apache WS XmlSchema library.</p> * * <p>Having said that, this {@link JAXBContext} need to analyze the managed classes with JAXB annotations in order to:<ul> * <li>create recipes for marshalling/unmarshalling (the only relation to (TODO: check) XSD is with {@code xsi:type} attributes and XSD type <b>names</b>)</li> * <li>create metadata describing as many properties of XML Schema Components as possible with what JAXB annotations (and maybe new, future JAXB3 annotations?) provide</li> * </ul></p> * * @author Grzegorz Grzybek */ @SuppressWarnings("deprecation") public class SweJaxbContext extends JAXBContext implements TypedPatternRegistry { private static Logger log = LoggerFactory.getLogger(SweJaxbContext.class.getName()); /** * Mapping of Java classes to OXM metadata. This metadata is about how Java class maps to a given XML Schema type (simple or complex) * This mapping doesn't deal with XML Schema elements - these are determined for each marshal operation, not at the context creation time. */ Map<Class<?>, TypedPattern<?>> patterns = new LinkedHashMap<Class<?>, TypedPattern<?>>(); /** * Mapping of XSD type QNames to {@link TypedPattern}s. This is useful for finding patterns for Java properties annotated * with {@link XmlSchemaType} annotation. */ Map<QName, TypedPattern<?>> patternsForTypeQNames = new LinkedHashMap<QName, TypedPattern<?>>(); /** * Mapping of {@link XmlRootElement} annotated classes to element patterns. */ Map<Class<?>, ElementPattern<?>> rootPatterns = new LinkedHashMap<Class<?>, ElementPattern<?>>(); /** * Mapping of QNames of root elements to element patterns. It's just another keyset of {@code rootPatterns}. */ Map<QName, ElementPattern<?>> rootPatternsForQNames = new LinkedHashMap<QName, ElementPattern<?>>(); /** * Metadata of packages */ private Map<String, JaxbMetadata> package2meta = new HashMap<String, JaxbMetadata>(); /** * <p>Main initiailzation method of {@link JAXBContext} - isn't it clean?</p> * * @param classesToBeBound * @param properties */ SweJaxbContext(Class<?>[] classesToBeBound, Map<String, ?> properties) { // built-in types BuiltInMappings.initialize(this.patterns, this.patternsForTypeQNames); // external types for (Class<?> cl : classesToBeBound) { this.determineAndCacheXmlPattern(cl); } } /* (non-Javadoc) * @see javax.xml.bind.JAXBContext#createUnmarshaller() */ @Override public Unmarshaller createUnmarshaller() throws JAXBException { return new SweJaxbUnmarshaller(this); } /* (non-Javadoc) * @see javax.xml.bind.JAXBContext#createMarshaller() */ @Override public Marshaller createMarshaller() throws JAXBException { return new SweJaxbMarshaller(this); } /* (non-Javadoc) * @see javax.xml.bind.JAXBContext#createValidator() */ @Override public Validator createValidator() throws JAXBException { throw new UnsupportedOperationException("Not implemented in " + this.getClass().getName()); } /* TypedPatternRegistry */ @Override @SuppressWarnings("unchecked") public <T> TypedPattern<T> findPatternByClass(Class<T> clazz) { TypedPattern<T> pattern = (TypedPattern<T>) this.patterns.get(clazz); if (pattern != null && !(pattern instanceof TemporaryTypedPattern)) return pattern; return null; } /* (non-Javadoc) * @see org.javelin.sws.ext.bind.TypedPatternRegistry#findTypedPattern(javax.xml.namespace.QName, java.lang.Class) */ @Override @SuppressWarnings("unchecked") public <T> TypedPattern<T> findPatternByType(QName typeName, Class<T> clazz) { return (TypedPattern<T>) this.patternsForTypeQNames.get(typeName); } /** * <p>Get a representation of a class and it's (JAXB2) metadata as a <i>pattern</i> of static and dynamic {@link XMLEvent XML events}.</p> * * <p>A class which is to be known by the registry should be directly convertible to a series of XML events. What is not mandatory here is * the root element of the marshalled object.</p> * * <p>The produced pattern is automatically cached in the registry. Also the metadata for {@link XmlRootElement} annotated classes is cached.</p> * * @param cl * @return */ @SuppressWarnings("unchecked") @Override public <T> TypedPattern<T> determineAndCacheXmlPattern(Class<T> cl) { // DESIGNFLAW: some check are done using map.containsKey(), some - using TypedPatternRegistry if (this.patterns.containsKey(cl)) { return (TypedPattern<T>) this.patterns.get(cl); } TypedPattern<T> pattern = null; log.trace("Mapping {} class to TypedPattern", cl.getName()); // defaults JaxbMetadata meta = this.package2meta.get(cl.getPackage().getName()); // package customization - cached if (!this.package2meta.containsKey(cl.getPackage().getName())) { meta = JaxbMetadata.defaultForPackage(cl.getPackage()); // determine package-level default accessor type XmlAccessorType xmlAccessorType = AnnotationUtils.getAnnotation(cl.getPackage(), XmlAccessorType.class); XmlSchema xmlSchema = AnnotationUtils.getAnnotation(cl.getPackage(), XmlSchema.class); if (xmlAccessorType != null) meta.setXmlAccessType(xmlAccessorType.value()); if (xmlSchema != null) { meta.setNamespace(xmlSchema.namespace()); meta.setElementForm(xmlSchema.elementFormDefault()); meta.setAttributeForm(xmlSchema.attributeFormDefault()); } this.package2meta.put(cl.getPackage().getName(), meta); } // class customization - not cached XmlAccessorType xmlAccessorType = AnnotationUtils.findAnnotation(cl, XmlAccessorType.class); XmlType xmlType = AnnotationUtils.getAnnotation(cl, XmlType.class); XmlAccessType xmlAccessType = xmlAccessorType == null ? meta.getXmlAccessType() : xmlAccessorType.value(); String namespace = xmlType == null || "##default".equals(xmlType.namespace()) ? meta.getNamespace() : xmlType.namespace(); QName typeName = new QName(namespace, xmlType == null || "##default".equals(xmlType.name()) ? cl.getSimpleName() : xmlType.name()); // before stepping into the class we'll add TemporaryTypedPattern to the mapping to be able to analyze cross-dependent classes TemporaryTypedPattern<T> txp = TemporaryTypedPattern.newTemporaryTypedPattern(typeName, cl); this.patterns.put(cl, txp); PropertyCallback<T> pc = new PropertyCallback<T>(this, cl, typeName, xmlAccessType, meta.getElementForm(), meta.getAttributeForm()); // this is where the magic happens pattern = pc.analyze(); txp.setRealPattern(pattern); // cache known global elements XmlRootElement xmlRootElement = AnnotationUtils.findAnnotation(cl, XmlRootElement.class); if (xmlRootElement != null) { String rootElementNamespace = "##default".equals(xmlRootElement.namespace()) ? meta.getNamespace() : xmlRootElement.namespace(); String rootElementName = "##default".equals(xmlRootElement.name()) ? cl.getSimpleName() : xmlRootElement.name(); QName rootQName = new QName(rootElementNamespace, rootElementName); ElementPattern<?> elementPattern = ElementPattern.newElementPattern(rootQName, pattern); this.rootPatterns.put(cl, elementPattern); this.rootPatternsForQNames.put(rootQName, elementPattern); } this.patterns.put(pattern.getJavaType(), pattern); this.patternsForTypeQNames.put(pattern.getSchemaType(), pattern); // see also XmlSeeAlso xmlSeeAlso = AnnotationUtils.findAnnotation(cl, XmlSeeAlso.class); if (xmlSeeAlso != null) { for (Class<?> c : xmlSeeAlso.value()) { log.trace("Analyzing @XmlSeeAlso class {}", c.getName()); this.determineAndCacheXmlPattern(c); } } return pattern; } }