Skip to content

Commit e664779

Browse files
committed
Do not require bound classes or context for Jaxb2Marshaller.
RestTemplate now registers JAXB2 and Jackson by default, if found on the classpath.
1 parent 01ce468 commit e664779

File tree

5 files changed

+372
-4
lines changed

5 files changed

+372
-4
lines changed

org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,11 @@
2020
import java.nio.charset.Charset;
2121
import java.util.List;
2222

23-
import javax.xml.bind.Marshaller;
24-
import javax.xml.bind.PropertyException;
25-
2623
import org.codehaus.jackson.JsonEncoding;
2724
import org.codehaus.jackson.JsonGenerator;
28-
import org.codehaus.jackson.type.JavaType;
2925
import org.codehaus.jackson.map.ObjectMapper;
3026
import org.codehaus.jackson.map.type.TypeFactory;
27+
import org.codehaus.jackson.type.JavaType;
3128

3229
import org.springframework.http.HttpInputMessage;
3330
import org.springframework.http.HttpOutputMessage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2002-2009 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.converter.xml;
18+
19+
import java.util.concurrent.ConcurrentHashMap;
20+
import java.util.concurrent.ConcurrentMap;
21+
import javax.xml.bind.JAXBContext;
22+
import javax.xml.bind.JAXBException;
23+
import javax.xml.bind.Marshaller;
24+
import javax.xml.bind.Unmarshaller;
25+
26+
import org.springframework.http.converter.HttpMessageConversionException;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} that
31+
* use JAXB2. Creates {@link JAXBContext} object lazily.
32+
*
33+
* @author Arjen Poutsma
34+
* @since 3.0
35+
*/
36+
public abstract class AbstractJaxb2HttpMessageConverter<T> extends AbstractXmlHttpMessageConverter<T> {
37+
38+
private final ConcurrentMap<Class, JAXBContext> jaxbContexts = new ConcurrentHashMap<Class, JAXBContext>();
39+
40+
/**
41+
* Creates a new {@link Marshaller} for the given class.
42+
*
43+
* @param clazz the class to create the marshaller for
44+
* @return the {@code Marshaller}
45+
* @throws HttpMessageConversionException in case of JAXB errors
46+
*/
47+
protected final Marshaller createMarshaller(Class clazz) {
48+
try {
49+
JAXBContext jaxbContext = getJaxbContext(clazz);
50+
return jaxbContext.createMarshaller();
51+
}
52+
catch (JAXBException ex) {
53+
throw new HttpMessageConversionException(
54+
"Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
55+
}
56+
}
57+
58+
/**
59+
* Creates a new {@link Unmarshaller} for the given class.
60+
*
61+
* @param clazz the class to create the unmarshaller for
62+
* @return the {@code Unmarshaller}
63+
* @throws HttpMessageConversionException in case of JAXB errors
64+
*/
65+
protected final Unmarshaller createUnmarshaller(Class clazz) throws JAXBException {
66+
try {
67+
JAXBContext jaxbContext = getJaxbContext(clazz);
68+
return jaxbContext.createUnmarshaller();
69+
}
70+
catch (JAXBException ex) {
71+
throw new HttpMessageConversionException(
72+
"Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex);
73+
}
74+
}
75+
76+
/**
77+
* Returns a {@link JAXBContext} for the given class.
78+
*
79+
* @param clazz the class to return the context for
80+
* @return the {@code JAXBContext}
81+
* @throws HttpMessageConversionException in case of JAXB errors
82+
*/
83+
protected final JAXBContext getJaxbContext(Class clazz) {
84+
Assert.notNull(clazz, "'clazz' must not be null");
85+
JAXBContext jaxbContext = jaxbContexts.get(clazz);
86+
if (jaxbContext == null) {
87+
try {
88+
jaxbContext = JAXBContext.newInstance(clazz);
89+
jaxbContexts.putIfAbsent(clazz, jaxbContext);
90+
}
91+
catch (JAXBException ex) {
92+
throw new HttpMessageConversionException(
93+
"Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex);
94+
}
95+
}
96+
return jaxbContext;
97+
}
98+
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2002-2009 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.converter.xml;
18+
19+
import java.io.IOException;
20+
import javax.xml.bind.JAXBElement;
21+
import javax.xml.bind.JAXBException;
22+
import javax.xml.bind.MarshalException;
23+
import javax.xml.bind.Marshaller;
24+
import javax.xml.bind.PropertyException;
25+
import javax.xml.bind.UnmarshalException;
26+
import javax.xml.bind.Unmarshaller;
27+
import javax.xml.bind.annotation.XmlRootElement;
28+
import javax.xml.bind.annotation.XmlType;
29+
import javax.xml.transform.Result;
30+
import javax.xml.transform.Source;
31+
32+
import org.springframework.core.annotation.AnnotationUtils;
33+
import org.springframework.http.HttpHeaders;
34+
import org.springframework.http.MediaType;
35+
import org.springframework.http.converter.HttpMessageConversionException;
36+
import org.springframework.http.converter.HttpMessageNotReadableException;
37+
import org.springframework.http.converter.HttpMessageNotWritableException;
38+
import org.springframework.util.ClassUtils;
39+
40+
/**
41+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read
42+
* and write XML using JAXB2.
43+
*
44+
* <p>This converter can read classes annotated with {@link XmlRootElement} and {@link XmlType}, and write classes
45+
* annotated with with {@link XmlRootElement}, or subclasses thereof.
46+
*
47+
* @author Arjen Poutsma
48+
* @since 3.0
49+
*/
50+
public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {
51+
52+
@Override
53+
public boolean canRead(Class<?> clazz, MediaType mediaType) {
54+
return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) &&
55+
isSupported(mediaType);
56+
}
57+
58+
@Override
59+
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
60+
return AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && isSupported(mediaType);
61+
}
62+
63+
@Override
64+
protected boolean supports(Class<?> clazz) {
65+
// should not be called, since we override canRead/Write
66+
throw new UnsupportedOperationException();
67+
}
68+
69+
@Override
70+
protected Object readFromSource(Class<Object> clazz, HttpHeaders headers, Source source) throws IOException {
71+
try {
72+
Unmarshaller unmarshaller = createUnmarshaller(clazz);
73+
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
74+
return unmarshaller.unmarshal(source);
75+
}
76+
else {
77+
JAXBElement<Object> jaxbElement = unmarshaller.unmarshal(source, clazz);
78+
return jaxbElement.getValue();
79+
}
80+
}
81+
catch (UnmarshalException ex) {
82+
throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex);
83+
84+
}
85+
catch (JAXBException ex) {
86+
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
87+
}
88+
}
89+
90+
@Override
91+
protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
92+
try {
93+
Class clazz = ClassUtils.getUserClass(o);
94+
Marshaller marshaller = createMarshaller(clazz);
95+
setCharset(headers.getContentType(), marshaller);
96+
marshaller.marshal(o, result);
97+
}
98+
catch (MarshalException ex) {
99+
throw new HttpMessageNotWritableException("Could not marshal [" + o + "]: " + ex.getMessage(), ex);
100+
}
101+
catch (JAXBException ex) {
102+
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex);
103+
}
104+
}
105+
106+
private void setCharset(MediaType contentType, Marshaller marshaller) throws PropertyException {
107+
if (contentType != null && contentType.getCharSet() != null) {
108+
marshaller.setProperty(Marshaller.JAXB_ENCODING, contentType.getCharSet().name());
109+
}
110+
}
111+
112+
}

org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@
3535
import org.springframework.http.converter.FormHttpMessageConverter;
3636
import org.springframework.http.converter.HttpMessageConverter;
3737
import org.springframework.http.converter.StringHttpMessageConverter;
38+
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
3839
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
40+
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
3941
import org.springframework.util.Assert;
42+
import org.springframework.util.ClassUtils;
4043
import org.springframework.web.util.UriTemplate;
4144

4245
/**
@@ -98,6 +101,13 @@
98101
*/
99102
public class RestTemplate extends HttpAccessor implements RestOperations {
100103

104+
private static final boolean jaxb2Present =
105+
ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader());
106+
107+
private static final boolean jacksonPresent =
108+
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestTemplate.class.getClassLoader()) &&
109+
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader());
110+
101111
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
102112

103113
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
@@ -110,6 +120,12 @@ public RestTemplate() {
110120
this.messageConverters.add(new StringHttpMessageConverter());
111121
this.messageConverters.add(new FormHttpMessageConverter());
112122
this.messageConverters.add(new SourceHttpMessageConverter());
123+
if (jaxb2Present) {
124+
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
125+
}
126+
if (jacksonPresent) {
127+
this.messageConverters.add(new MappingJacksonHttpMessageConverter());
128+
}
113129
}
114130

115131
/**

0 commit comments

Comments
 (0)