View Javadoc
1   /*
2    * Copyright 2022 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.bremersee.xml.http.converter;
18  
19  import static java.util.Objects.isNull;
20  
21  import java.util.Set;
22  import jakarta.xml.bind.JAXBElement;
23  import jakarta.xml.bind.JAXBException;
24  import jakarta.xml.bind.MarshalException;
25  import jakarta.xml.bind.Marshaller;
26  import jakarta.xml.bind.PropertyException;
27  import jakarta.xml.bind.UnmarshalException;
28  import jakarta.xml.bind.Unmarshaller;
29  import jakarta.xml.bind.annotation.XmlRootElement;
30  import javax.xml.transform.Result;
31  import javax.xml.transform.Source;
32  import org.bremersee.xml.JaxbContextBuilder;
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.xml.AbstractXmlHttpMessageConverter;
37  import org.springframework.lang.NonNull;
38  import org.springframework.lang.Nullable;
39  import org.springframework.util.Assert;
40  
41  /**
42   * The jaxb http message converter.
43   *
44   * @author Arjen Poutsma, Juergen Hoeller, Rossen Stoyanchev, Christian Bremer
45   * @see org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter
46   */
47  public class Jaxb2HttpMessageConverter extends AbstractXmlHttpMessageConverter<Object> {
48  
49    private final JaxbContextBuilder jaxbContextBuilder;
50  
51    private final Set<Class<?>> ignoreReadingClasses;
52  
53    private final Set<Class<?>> ignoreWritingClasses;
54  
55    /**
56     * Instantiates a new jaxb http message converter.
57     *
58     * @param jaxbContextBuilder the jaxb context builder
59     */
60    public Jaxb2HttpMessageConverter(JaxbContextBuilder jaxbContextBuilder) {
61      this(jaxbContextBuilder, null, null);
62    }
63  
64    /**
65     * Instantiates a new Jaxb 2 http message converter.
66     *
67     * @param jaxbContextBuilder the jaxb context builder
68     * @param ignoreReadingClasses ignore reading classes
69     * @param ignoreWritingClasses ignore writing classes
70     */
71    public Jaxb2HttpMessageConverter(
72        JaxbContextBuilder jaxbContextBuilder,
73        Set<Class<?>> ignoreReadingClasses,
74        Set<Class<?>> ignoreWritingClasses) {
75      Assert.notNull(jaxbContextBuilder, "JaxbContextBuilder must be present.");
76      this.jaxbContextBuilder = jaxbContextBuilder;
77      this.ignoreReadingClasses = isNull(ignoreReadingClasses) ? Set.of() : ignoreReadingClasses;
78      this.ignoreWritingClasses = isNull(ignoreWritingClasses) ? Set.of() : ignoreWritingClasses;
79    }
80  
81    @Override
82    public boolean canRead(@NonNull Class<?> clazz, @Nullable MediaType mediaType) {
83      return !ignoreReadingClasses.contains(clazz)
84          && jaxbContextBuilder.canUnmarshal(clazz)
85          && this.canRead(mediaType);
86    }
87  
88    @Override
89    public boolean canWrite(@NonNull Class<?> clazz, @Nullable MediaType mediaType) {
90      return !ignoreWritingClasses.contains(clazz)
91          && jaxbContextBuilder.canMarshal(clazz)
92          && this.canWrite(mediaType);
93    }
94  
95    @Override
96    protected boolean supports(@NonNull Class<?> clazz) {
97      throw new UnsupportedOperationException();
98    }
99  
100   @NonNull
101   @Override
102   protected Object readFromSource(
103       @NonNull Class<?> clazz,
104       @NonNull HttpHeaders headers,
105       @NonNull Source source) throws Exception {
106 
107     try {
108       // processSource of Jaxb2RootElementHttpMessageConverter causes MalformedUrlException
109       Unmarshaller unmarshaller = jaxbContextBuilder.buildUnmarshaller(clazz);
110       if (clazz.isAnnotationPresent(XmlRootElement.class)) {
111         return unmarshaller.unmarshal(source);
112       } else {
113         JAXBElement<?> jaxbElement = unmarshaller.unmarshal(source, clazz);
114         return jaxbElement.getValue();
115       }
116     } catch (UnmarshalException unmarshalException) {
117       throw unmarshalException;
118     } catch (JAXBException jaxbException) {
119       throw new HttpMessageConversionException("Invalid JAXB setup: "
120           + jaxbException.getMessage(), jaxbException);
121     }
122   }
123 
124   @Override
125   protected void writeToResult(
126       @NonNull Object o,
127       HttpHeaders headers,
128       @NonNull Result result) throws Exception {
129 
130     try {
131       Marshaller marshaller = jaxbContextBuilder.buildMarshaller(o);
132       this.setCharset(headers.getContentType(), marshaller);
133       marshaller.marshal(o, result);
134     } catch (MarshalException marshalException) {
135       throw marshalException;
136     } catch (JAXBException jaxbException) {
137       throw new HttpMessageConversionException("Invalid JAXB setup: "
138           + jaxbException.getMessage(), jaxbException);
139     }
140   }
141 
142   private void setCharset(
143       @Nullable MediaType contentType,
144       Marshaller marshaller) throws PropertyException {
145 
146     if (contentType != null && contentType.getCharset() != null) {
147       marshaller.setProperty("jaxb.encoding", contentType.getCharset().name());
148     }
149   }
150 
151 }