1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.http.codec.xml;
18
19 import java.util.ArrayList;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.function.BiConsumer;
24 import javax.xml.XMLConstants;
25 import javax.xml.bind.JAXBElement;
26 import javax.xml.bind.JAXBException;
27 import javax.xml.bind.UnmarshalException;
28 import javax.xml.bind.Unmarshaller;
29 import javax.xml.bind.annotation.XmlRootElement;
30 import javax.xml.bind.annotation.XmlSchema;
31 import javax.xml.bind.annotation.XmlType;
32 import javax.xml.namespace.QName;
33 import javax.xml.stream.XMLEventReader;
34 import javax.xml.stream.XMLInputFactory;
35 import javax.xml.stream.XMLStreamException;
36 import javax.xml.stream.events.XMLEvent;
37 import org.bremersee.xml.JaxbContextBuilder;
38 import org.reactivestreams.Publisher;
39 import org.springframework.core.ResolvableType;
40 import org.springframework.core.codec.AbstractDecoder;
41 import org.springframework.core.codec.CodecException;
42 import org.springframework.core.codec.DecodingException;
43 import org.springframework.core.codec.Hints;
44 import org.springframework.core.io.buffer.DataBuffer;
45 import org.springframework.core.io.buffer.DataBufferLimitException;
46 import org.springframework.core.io.buffer.DataBufferUtils;
47 import org.springframework.core.log.LogFormatUtils;
48 import org.springframework.http.MediaType;
49 import org.springframework.http.codec.xml.XmlEventDecoder;
50 import org.springframework.lang.NonNull;
51 import org.springframework.lang.Nullable;
52 import org.springframework.util.Assert;
53 import org.springframework.util.ClassUtils;
54 import org.springframework.util.MimeType;
55 import org.springframework.util.MimeTypeUtils;
56 import org.springframework.util.xml.StaxUtils;
57 import org.springframework.validation.annotation.Validated;
58 import reactor.core.Exceptions;
59 import reactor.core.publisher.Flux;
60 import reactor.core.publisher.Mono;
61 import reactor.core.publisher.SynchronousSink;
62
63
64
65
66
67
68
69
70
71
72 @Validated
73 public class ReactiveJaxbDecoder extends AbstractDecoder<Object> {
74
75
76
77
78
79
80
81
82
83 private static final String JAXB_DEFAULT_ANNOTATION_VALUE = "##default";
84
85 private static final XMLInputFactory inputFactory = StaxUtils.createDefensiveInputFactory();
86
87
88 private final XmlEventDecoder xmlEventDecoder = new XmlEventDecoder();
89
90 private final JaxbContextBuilder jaxbContextBuilder;
91
92 private int maxInMemorySize = 256 * 1024;
93
94
95
96
97
98
99 public ReactiveJaxbDecoder(final JaxbContextBuilder jaxbContextBuilder) {
100 super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML,
101 new MediaType("application", "*+xml"));
102 this.jaxbContextBuilder = jaxbContextBuilder != null
103 ? jaxbContextBuilder
104 : JaxbContextBuilder.builder()
105 .withCanUnmarshal(JaxbContextBuilder.CAN_UNMARSHAL_ALL);
106 }
107
108
109
110
111
112
113
114
115
116
117 public void setMaxInMemorySize(int byteCount) {
118 this.maxInMemorySize = byteCount;
119 this.xmlEventDecoder.setMaxInMemorySize(byteCount);
120 }
121
122
123
124
125
126
127 public int getMaxInMemorySize() {
128 return this.maxInMemorySize;
129 }
130
131
132 @Override
133 public boolean canDecode(@NonNull ResolvableType elementType, @Nullable MimeType mimeType) {
134 if (super.canDecode(elementType, mimeType)) {
135 final Class<?> outputClass = elementType.getRawClass();
136 return jaxbContextBuilder.canUnmarshal(outputClass);
137 } else {
138 return false;
139 }
140 }
141
142 @NonNull
143 @Override
144 public Flux<Object> decode(@NonNull Publisher<DataBuffer> inputStream, ResolvableType elementType,
145 @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
146
147 Flux<XMLEvent> xmlEventFlux = this.xmlEventDecoder.decode(
148 inputStream, ResolvableType.forClass(XMLEvent.class), mimeType, hints);
149
150 Class<?> outputClass = elementType.toClass();
151 QName typeName = toQName(outputClass);
152 Flux<List<XMLEvent>> splitEvents = split(xmlEventFlux, typeName);
153
154 return splitEvents.map(events -> {
155 Object value = unmarshal(events, outputClass);
156 LogFormatUtils.traceDebug(logger, traceOn -> {
157 String formatted = LogFormatUtils.formatValue(value, !traceOn);
158 return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]";
159 });
160 return value;
161 });
162 }
163
164 @NonNull
165 @Override
166 @SuppressWarnings({"rawtypes", "unchecked", "cast"})
167
168 public Object decode(DataBuffer dataBuffer, ResolvableType targetType,
169 @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {
170
171 try {
172 Iterator eventReader = inputFactory.createXMLEventReader(dataBuffer.asInputStream());
173 List<XMLEvent> events = new ArrayList<>();
174 eventReader.forEachRemaining(event -> events.add((XMLEvent) event));
175 return unmarshal(events, targetType.toClass());
176 } catch (XMLStreamException ex) {
177 throw Exceptions.propagate(ex);
178 } finally {
179 DataBufferUtils.release(dataBuffer);
180 }
181 }
182
183 @NonNull
184 @Override
185 public Mono<Object> decodeToMono(@NonNull Publisher<DataBuffer> input, @NonNull ResolvableType elementType,
186 @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
187
188
189 return DataBufferUtils.join(input, this.maxInMemorySize)
190 .map(dataBuffer -> decode(dataBuffer, elementType, mimeType, hints));
191 }
192
193 private Object unmarshal(List<XMLEvent> events, Class<?> outputClass) {
194 try {
195 Unmarshaller unmarshaller = jaxbContextBuilder.buildUnmarshaller(outputClass);
196 XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
197 if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
198 return unmarshaller.unmarshal(eventReader);
199 } else {
200 JAXBElement<?> jaxbElement = unmarshaller.unmarshal(eventReader, outputClass);
201 return jaxbElement.getValue();
202 }
203 } catch (UnmarshalException ex) {
204 throw new DecodingException("Could not unmarshal XML to " + outputClass, ex);
205 } catch (JAXBException ex) {
206 throw new CodecException("Invalid JAXB configuration", ex);
207 }
208 }
209
210
211
212
213
214
215
216 QName toQName(Class<?> outputClass) {
217 String localPart;
218 String namespaceUri;
219
220 if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
221 XmlRootElement annotation = outputClass.getAnnotation(XmlRootElement.class);
222 localPart = annotation.name();
223 namespaceUri = annotation.namespace();
224 } else if (outputClass.isAnnotationPresent(XmlType.class)) {
225 XmlType annotation = outputClass.getAnnotation(XmlType.class);
226 localPart = annotation.name();
227 namespaceUri = annotation.namespace();
228 } else {
229 throw new IllegalArgumentException("Output class [" + outputClass.getName()
230 + "] is neither annotated with @XmlRootElement nor @XmlType");
231 }
232
233 if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(localPart)) {
234 localPart = ClassUtils.getShortNameAsProperty(outputClass);
235 }
236 if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(namespaceUri)) {
237 Package outputClassPackage = outputClass.getPackage();
238 if (outputClassPackage != null && outputClassPackage.isAnnotationPresent(XmlSchema.class)) {
239 XmlSchema annotation = outputClassPackage.getAnnotation(XmlSchema.class);
240 namespaceUri = annotation.namespace();
241 } else {
242 namespaceUri = XMLConstants.NULL_NS_URI;
243 }
244 }
245 return new QName(namespaceUri, localPart);
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273 Flux<List<XMLEvent>> split(Flux<XMLEvent> xmlEventFlux, QName desiredName) {
274 return xmlEventFlux.handle(new SplitHandler(desiredName));
275 }
276
277
278 private static class SplitHandler implements
279 BiConsumer<XMLEvent, SynchronousSink<List<XMLEvent>>> {
280
281 private final QName desiredName;
282
283 private List<XMLEvent> events;
284
285 private int elementDepth = 0;
286
287 private int barrier = Integer.MAX_VALUE;
288
289
290
291
292
293
294 public SplitHandler(QName desiredName) {
295 this.desiredName = desiredName;
296 }
297
298 @Override
299 public void accept(XMLEvent event, SynchronousSink<List<XMLEvent>> sink) {
300 if (event.isStartElement()) {
301 if (this.barrier == Integer.MAX_VALUE) {
302 QName startElementName = event.asStartElement().getName();
303 if (this.desiredName.equals(startElementName)) {
304 this.events = new ArrayList<>();
305 this.barrier = this.elementDepth;
306 }
307 }
308 this.elementDepth++;
309 }
310 if (this.elementDepth > this.barrier) {
311 Assert.state(this.events != null, "No XMLEvent List");
312 this.events.add(event);
313 }
314 if (event.isEndElement()) {
315 this.elementDepth--;
316 if (this.elementDepth == this.barrier) {
317 this.barrier = Integer.MAX_VALUE;
318 Assert.state(this.events != null, "No XMLEvent List");
319 sink.next(this.events);
320 }
321 }
322 }
323 }
324
325 }