SPR-8986 Add the ability to Scan Packages for JAXB Marshalling
Jaxb2Marshaller now has the capability to scan for classes annotated with JAXB2 annotations.
This commit is contained in:
parent
bcd8355e61
commit
79f32c7f33
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-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.springframework.oxm.jaxb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.xml.bind.annotation.XmlEnum;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.XmlSeeAlso;
|
||||||
|
import javax.xml.bind.annotation.XmlType;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||||
|
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||||
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
|
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||||
|
import org.springframework.core.type.filter.TypeFilter;
|
||||||
|
import org.springframework.oxm.UncategorizedMappingException;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for {@link Jaxb2Marshaller} that scans given packages for classes marked with JAXB2 annotations.
|
||||||
|
*
|
||||||
|
* @author Arjen Poutsma
|
||||||
|
* @author David Harrigan
|
||||||
|
* @see #scanPackages()
|
||||||
|
*/
|
||||||
|
class ClassPathJaxb2TypeScanner {
|
||||||
|
|
||||||
|
private static final String RESOURCE_PATTERN = "/**/*.class";
|
||||||
|
|
||||||
|
private final TypeFilter[] jaxb2TypeFilters =
|
||||||
|
new TypeFilter[]{new AnnotationTypeFilter(XmlRootElement.class, false),
|
||||||
|
new AnnotationTypeFilter(XmlType.class, false), new AnnotationTypeFilter(XmlSeeAlso.class, false),
|
||||||
|
new AnnotationTypeFilter(XmlEnum.class, false)};
|
||||||
|
|
||||||
|
private final String[] packagesToScan;
|
||||||
|
|
||||||
|
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||||
|
|
||||||
|
private List<Class<?>> jaxb2Classes = new ArrayList<Class<?>>();
|
||||||
|
|
||||||
|
/** Constructs a new {@code ClassPathJaxb2TypeScanner} for the given packages. */
|
||||||
|
ClassPathJaxb2TypeScanner(String[] packagesToScan) {
|
||||||
|
Assert.notEmpty(packagesToScan, "'packagesToScan' must not be empty");
|
||||||
|
this.packagesToScan = packagesToScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setResourceLoader(ResourceLoader resourceLoader) {
|
||||||
|
if (resourceLoader != null) {
|
||||||
|
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the JAXB2 classes found in the specified packages. */
|
||||||
|
Class<?>[] getJaxb2Classes() {
|
||||||
|
return jaxb2Classes.toArray(new Class<?>[jaxb2Classes.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the packages for classes marked with JAXB2 annotations.
|
||||||
|
*
|
||||||
|
* @throws UncategorizedMappingException in case of errors
|
||||||
|
*/
|
||||||
|
void scanPackages() throws UncategorizedMappingException {
|
||||||
|
try {
|
||||||
|
for (String packageToScan : packagesToScan) {
|
||||||
|
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
|
||||||
|
ClassUtils.convertClassNameToResourcePath(packageToScan) + RESOURCE_PATTERN;
|
||||||
|
Resource[] resources = resourcePatternResolver.getResources(pattern);
|
||||||
|
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
|
||||||
|
if (isJaxb2Class(metadataReader, metadataReaderFactory)) {
|
||||||
|
String className = metadataReader.getClassMetadata().getClassName();
|
||||||
|
Class<?> jaxb2AnnotatedClass = resourcePatternResolver.getClassLoader().loadClass(className);
|
||||||
|
jaxb2Classes.add(jaxb2AnnotatedClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new UncategorizedMappingException("Failed to scan classpath for unlisted classes", ex);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex) {
|
||||||
|
throw new UncategorizedMappingException("Failed to load annotated classes from classpath", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isJaxb2Class(MetadataReader reader, MetadataReaderFactory factory) throws IOException {
|
||||||
|
for (TypeFilter filter : jaxb2TypeFilters) {
|
||||||
|
if (filter.match(reader, factory)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.oxm.jaxb;
|
package org.springframework.oxm.jaxb;
|
||||||
|
|
||||||
import java.awt.Image;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -75,8 +75,10 @@ import org.xml.sax.helpers.XMLReaderFactory;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.context.ResourceLoaderAware;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.oxm.GenericMarshaller;
|
import org.springframework.oxm.GenericMarshaller;
|
||||||
import org.springframework.oxm.GenericUnmarshaller;
|
import org.springframework.oxm.GenericUnmarshaller;
|
||||||
import org.springframework.oxm.MarshallingFailureException;
|
import org.springframework.oxm.MarshallingFailureException;
|
||||||
|
|
@ -117,7 +119,7 @@ import org.springframework.util.xml.StaxUtils;
|
||||||
*/
|
*/
|
||||||
public class Jaxb2Marshaller
|
public class Jaxb2Marshaller
|
||||||
implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller, BeanClassLoaderAware,
|
implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller, BeanClassLoaderAware,
|
||||||
InitializingBean {
|
ResourceLoaderAware, InitializingBean {
|
||||||
|
|
||||||
private static final String CID = "cid:";
|
private static final String CID = "cid:";
|
||||||
|
|
||||||
|
|
@ -130,6 +132,8 @@ public class Jaxb2Marshaller
|
||||||
private String contextPath;
|
private String contextPath;
|
||||||
|
|
||||||
private Class<?>[] classesToBeBound;
|
private Class<?>[] classesToBeBound;
|
||||||
|
|
||||||
|
private String[] packagesToScan;
|
||||||
|
|
||||||
private Map<String, ?> jaxbContextProperties;
|
private Map<String, ?> jaxbContextProperties;
|
||||||
|
|
||||||
|
|
@ -153,6 +157,8 @@ public class Jaxb2Marshaller
|
||||||
|
|
||||||
private ClassLoader beanClassLoader;
|
private ClassLoader beanClassLoader;
|
||||||
|
|
||||||
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
private JAXBContext jaxbContext;
|
private JAXBContext jaxbContext;
|
||||||
|
|
||||||
private Schema schema;
|
private Schema schema;
|
||||||
|
|
@ -175,6 +181,8 @@ public class Jaxb2Marshaller
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a JAXB context path.
|
* Set a JAXB context path.
|
||||||
|
* <p>Setting this property, {@link #setClassesToBeBound "classesToBeBound"}, or
|
||||||
|
* {@link #setPackagesToScan "packagesToScan"} is required.
|
||||||
*/
|
*/
|
||||||
public void setContextPath(String contextPath) {
|
public void setContextPath(String contextPath) {
|
||||||
Assert.hasText(contextPath, "'contextPath' must not be null");
|
Assert.hasText(contextPath, "'contextPath' must not be null");
|
||||||
|
|
@ -190,7 +198,8 @@ public class Jaxb2Marshaller
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the list of Java classes to be recognized by a newly created JAXBContext.
|
* Set the list of Java classes to be recognized by a newly created JAXBContext.
|
||||||
* Setting this property or {@link #setContextPath "contextPath"} is required.
|
* <p>Setting this property, {@link #setContextPath "contextPath"}, or
|
||||||
|
* {@link #setPackagesToScan "packagesToScan"} is required.
|
||||||
*/
|
*/
|
||||||
public void setClassesToBeBound(Class<?>... classesToBeBound) {
|
public void setClassesToBeBound(Class<?>... classesToBeBound) {
|
||||||
Assert.notEmpty(classesToBeBound, "'classesToBeBound' must not be empty");
|
Assert.notEmpty(classesToBeBound, "'classesToBeBound' must not be empty");
|
||||||
|
|
@ -204,6 +213,23 @@ public class Jaxb2Marshaller
|
||||||
return this.classesToBeBound;
|
return this.classesToBeBound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the packages to search using Spring-based scanning for classes with JAXB2 annotations in the classpath.
|
||||||
|
* <p>Setting this property, {@link #setContextPath "contextPath"}, or
|
||||||
|
* {@link #setClassesToBeBound "classesToBeBound"} is required. This is analogous to Spring's component-scan feature
|
||||||
|
* ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
|
||||||
|
*/
|
||||||
|
public void setPackagesToScan(String[] packagesToScan) {
|
||||||
|
this.packagesToScan = packagesToScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the packages to search for JAXB2 annotations.
|
||||||
|
*/
|
||||||
|
public String[] getPackagesToScan() {
|
||||||
|
return packagesToScan;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the <code>JAXBContext</code> properties. These implementation-specific
|
* Set the <code>JAXBContext</code> properties. These implementation-specific
|
||||||
* properties will be set on the underlying <code>JAXBContext</code>.
|
* properties will be set on the underlying <code>JAXBContext</code>.
|
||||||
|
|
@ -337,13 +363,23 @@ public class Jaxb2Marshaller
|
||||||
this.beanClassLoader = classLoader;
|
this.beanClassLoader = classLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
public final void afterPropertiesSet() throws Exception {
|
public final void afterPropertiesSet() throws Exception {
|
||||||
if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(getClassesToBeBound())) {
|
boolean hasContextPath = StringUtils.hasLength(getContextPath());
|
||||||
throw new IllegalArgumentException("Specify either 'contextPath' or 'classesToBeBound property'; not both");
|
boolean hasClassesToBeBound = !ObjectUtils.isEmpty(getClassesToBeBound());
|
||||||
|
boolean hasPackagesToScan = !ObjectUtils.isEmpty(getPackagesToScan());
|
||||||
|
|
||||||
|
if (hasContextPath && (hasClassesToBeBound || hasPackagesToScan) ||
|
||||||
|
(hasClassesToBeBound && hasPackagesToScan)) {
|
||||||
|
throw new IllegalArgumentException("Specify either 'contextPath', 'classesToBeBound', " +
|
||||||
|
"or 'packagesToScan'");
|
||||||
}
|
}
|
||||||
else if (!StringUtils.hasLength(getContextPath()) && ObjectUtils.isEmpty(getClassesToBeBound())) {
|
if (!hasContextPath && !hasClassesToBeBound && !hasPackagesToScan) {
|
||||||
throw new IllegalArgumentException("Setting either 'contextPath' or 'classesToBeBound' is required");
|
throw new IllegalArgumentException(
|
||||||
|
"Setting either 'contextPath', 'classesToBeBound', " + "or 'packagesToScan' is required");
|
||||||
}
|
}
|
||||||
if (!this.lazyInit) {
|
if (!this.lazyInit) {
|
||||||
getJaxbContext();
|
getJaxbContext();
|
||||||
|
|
@ -362,6 +398,9 @@ public class Jaxb2Marshaller
|
||||||
else if (!ObjectUtils.isEmpty(getClassesToBeBound())) {
|
else if (!ObjectUtils.isEmpty(getClassesToBeBound())) {
|
||||||
this.jaxbContext = createJaxbContextFromClasses();
|
this.jaxbContext = createJaxbContextFromClasses();
|
||||||
}
|
}
|
||||||
|
else if (!ObjectUtils.isEmpty(getPackagesToScan())) {
|
||||||
|
this.jaxbContext = createJaxbContextFromPackages();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (JAXBException ex) {
|
catch (JAXBException ex) {
|
||||||
throw convertJaxbException(ex);
|
throw convertJaxbException(ex);
|
||||||
|
|
@ -405,6 +444,26 @@ public class Jaxb2Marshaller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JAXBContext createJaxbContextFromPackages() throws JAXBException {
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
logger.info("Creating JAXBContext by scanning packages [" +
|
||||||
|
StringUtils.arrayToCommaDelimitedString(getPackagesToScan()) + "]");
|
||||||
|
}
|
||||||
|
ClassPathJaxb2TypeScanner scanner = new ClassPathJaxb2TypeScanner(getPackagesToScan());
|
||||||
|
scanner.setResourceLoader(this.resourceLoader);
|
||||||
|
scanner.scanPackages();
|
||||||
|
Class<?>[] jaxb2Classes = scanner.getJaxb2Classes();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Found JAXB2 classes: [" + StringUtils.arrayToCommaDelimitedString(jaxb2Classes) + "]");
|
||||||
|
}
|
||||||
|
if (this.jaxbContextProperties != null) {
|
||||||
|
return JAXBContext.newInstance(jaxb2Classes, this.jaxbContextProperties);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return JAXBContext.newInstance(jaxb2Classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException {
|
private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(this.schemaResources));
|
logger.debug("Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(this.schemaResources));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2002-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -279,6 +279,13 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests {
|
||||||
assertTrue("No XML written", writer.toString().length() > 0);
|
assertTrue("No XML written", writer.toString().length() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsPackagesToScan() throws Exception {
|
||||||
|
marshaller = new Jaxb2Marshaller();
|
||||||
|
marshaller.setPackagesToScan(new String[] {CONTEXT_PATH});
|
||||||
|
marshaller.afterPropertiesSet();
|
||||||
|
}
|
||||||
|
|
||||||
@XmlRootElement
|
@XmlRootElement
|
||||||
public static class DummyRootElement {
|
public static class DummyRootElement {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ Import-Template:
|
||||||
org.exolab.castor.*;version="[1.2.0, 2.0.0)";resolution:=optional,
|
org.exolab.castor.*;version="[1.2.0, 2.0.0)";resolution:=optional,
|
||||||
org.jibx.runtime.*;version="[1.1.5, 2.0.0)";resolution:=optional,
|
org.jibx.runtime.*;version="[1.1.5, 2.0.0)";resolution:=optional,
|
||||||
org.springframework.beans.*;version=${spring.osgi.range},
|
org.springframework.beans.*;version=${spring.osgi.range},
|
||||||
|
org.springframework.context.*;version=${spring.osgi.range},
|
||||||
org.springframework.core.*;version=${spring.osgi.range},
|
org.springframework.core.*;version=${spring.osgi.range},
|
||||||
org.springframework.util.*;version=${spring.osgi.range},
|
org.springframework.util.*;version=${spring.osgi.range},
|
||||||
org.w3c.dom.*;version="0",
|
org.w3c.dom.*;version="0",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue