1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.xml.http.codec;
18
19 import static java.util.Objects.isNull;
20
21 import java.io.OutputStream;
22 import java.util.Map;
23 import java.util.Set;
24 import jakarta.xml.bind.JAXBException;
25 import jakarta.xml.bind.MarshalException;
26 import jakarta.xml.bind.Marshaller;
27 import org.bremersee.xml.JaxbContextBuilder;
28 import org.springframework.core.ResolvableType;
29 import org.springframework.core.codec.AbstractSingleValueEncoder;
30 import org.springframework.core.codec.CodecException;
31 import org.springframework.core.codec.EncodingException;
32 import org.springframework.core.codec.Hints;
33 import org.springframework.core.io.buffer.DataBuffer;
34 import org.springframework.core.io.buffer.DataBufferFactory;
35 import org.springframework.core.io.buffer.DataBufferUtils;
36 import org.springframework.core.log.LogFormatUtils;
37 import org.springframework.lang.NonNull;
38 import org.springframework.lang.Nullable;
39 import org.springframework.util.Assert;
40 import org.springframework.util.MimeType;
41 import org.springframework.util.MimeTypeUtils;
42 import reactor.core.publisher.Flux;
43 import reactor.core.publisher.Mono;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class ReactiveJaxbEncoder extends AbstractSingleValueEncoder<Object> {
59
60 private final JaxbContextBuilder jaxbContextBuilder;
61
62 private final Set<Class<?>> ignoreWritingClasses;
63
64
65
66
67
68
69 public ReactiveJaxbEncoder(JaxbContextBuilder jaxbContextBuilder) {
70 this(jaxbContextBuilder, null);
71 }
72
73
74
75
76
77
78
79 public ReactiveJaxbEncoder(
80 JaxbContextBuilder jaxbContextBuilder,
81 Set<Class<?>> ignoreWritingClasses) {
82
83 super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML);
84 Assert.notNull(jaxbContextBuilder, "JaxbContextBuilder must be present.");
85 this.jaxbContextBuilder = jaxbContextBuilder;
86 this.ignoreWritingClasses = isNull(ignoreWritingClasses) ? Set.of() : ignoreWritingClasses;
87 }
88
89 @Override
90 public boolean canEncode(
91 @NonNull ResolvableType elementType,
92 @Nullable final MimeType mimeType) {
93
94 if (super.canEncode(elementType, mimeType)) {
95 final Class<?> outputClass = elementType.toClass();
96 return !ignoreWritingClasses.contains(outputClass)
97 && jaxbContextBuilder.canMarshal(outputClass);
98 } else {
99 return false;
100 }
101 }
102
103 @NonNull
104 @Override
105 protected Flux<DataBuffer> encode(
106 @NonNull Object value,
107 @NonNull DataBufferFactory bufferFactory,
108 @NonNull ResolvableType valueType,
109 @Nullable MimeType mimeType,
110 @Nullable Map<String, Object> hints) {
111
112
113 return Mono.fromCallable(() -> encodeValue(value, bufferFactory, valueType, mimeType, hints))
114 .flux();
115 }
116
117 @NonNull
118 @Override
119 public DataBuffer encodeValue(
120 @NonNull Object value,
121 @NonNull DataBufferFactory bufferFactory,
122 @NonNull ResolvableType valueType,
123 @Nullable MimeType mimeType,
124 @Nullable Map<String, Object> hints) {
125
126 if (!Hints.isLoggingSuppressed(hints)) {
127 LogFormatUtils.traceDebug(logger, traceOn -> {
128 String formatted = LogFormatUtils.formatValue(value, !traceOn);
129 return Hints.getLogPrefix(hints) + "Encoding [" + formatted + "]";
130 });
131 }
132
133 boolean release = true;
134 DataBuffer buffer = bufferFactory.allocateBuffer(1024);
135 try {
136 OutputStream outputStream = buffer.asOutputStream();
137 Marshaller marshaller = jaxbContextBuilder.buildMarshaller(value);
138 marshaller.marshal(value, outputStream);
139 release = false;
140 return buffer;
141 } catch (MarshalException ex) {
142 throw new EncodingException("Could not marshal " + value.getClass() + " to XML", ex);
143 } catch (JAXBException ex) {
144 throw new CodecException("Invalid JAXB configuration", ex);
145 } finally {
146 if (release) {
147 DataBufferUtils.release(buffer);
148 }
149 }
150 }
151
152 }