1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.xml;
18
19 import static org.springframework.util.ObjectUtils.isEmpty;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Objects;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.stream.Collectors;
31 import jakarta.xml.bind.JAXBContext;
32 import jakarta.xml.bind.JAXBException;
33 import jakarta.xml.bind.Marshaller;
34 import jakarta.xml.bind.Unmarshaller;
35 import jakarta.xml.bind.ValidationEventHandler;
36 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
37 import jakarta.xml.bind.attachment.AttachmentMarshaller;
38 import jakarta.xml.bind.attachment.AttachmentUnmarshaller;
39 import javax.xml.transform.Source;
40 import javax.xml.validation.Schema;
41 import lombok.ToString;
42 import org.springframework.util.ClassUtils;
43
44
45
46
47
48
49 @SuppressWarnings("SameNameButDifferent")
50 @ToString
51 class JaxbContextBuilderImpl implements JaxbContextBuilder {
52
53
54
55
56 private final Map<Object, JaxbContextData> jaxbContextDataMap = new ConcurrentHashMap<>();
57
58 private final Map<JaxbContextDetails, Schema> schemaCache = new ConcurrentHashMap<>();
59
60 private final Map<JaxbContextDetails, JAXBContext> jaxbContextCache
61 = new ConcurrentHashMap<>();
62
63 private JaxbDependenciesResolver dependenciesResolver = DEFAULT_DEPENDENCIES_RESOLVER;
64
65 private SchemaBuilder schemaBuilder = SchemaBuilder.newInstance();
66
67 private ClassLoader classLoader;
68
69 private boolean formattedOutput = true;
70
71 private SchemaMode schemaMode = SchemaMode.NEVER;
72
73 private List<XmlAdapter<?, ?>> xmlAdapters;
74
75 private AttachmentMarshaller attachmentMarshaller;
76
77 private AttachmentUnmarshaller attachmentUnmarshaller;
78
79 private ValidationEventHandler validationEventHandler;
80
81
82
83
84 JaxbContextBuilderImpl() {
85 }
86
87 private void clearCache() {
88 schemaCache.clear();
89 jaxbContextCache.clear();
90 }
91
92 @Override
93 public JaxbContextBuilder copy() {
94 JaxbContextBuilderImpl copy = new JaxbContextBuilderImpl();
95 copy.dependenciesResolver = dependenciesResolver;
96 copy.schemaMode = schemaMode;
97 copy.schemaCache.putAll(schemaCache);
98 copy.attachmentMarshaller = attachmentMarshaller;
99 copy.attachmentUnmarshaller = attachmentUnmarshaller;
100 copy.classLoader = classLoader;
101 copy.formattedOutput = formattedOutput;
102 copy.jaxbContextCache.putAll(jaxbContextCache);
103 copy.jaxbContextDataMap.putAll(jaxbContextDataMap);
104 copy.schemaBuilder = schemaBuilder.copy();
105 copy.validationEventHandler = validationEventHandler;
106 if (!isEmpty(xmlAdapters)) {
107 copy.xmlAdapters = new ArrayList<>(xmlAdapters);
108 }
109 return copy;
110 }
111
112 @Override
113 public JaxbContextBuilder withSchemaMode(SchemaMode schemaMode) {
114 if (!isEmpty(schemaMode)) {
115 this.schemaMode = schemaMode;
116 }
117 return this;
118 }
119
120 @Override
121 public JaxbContextBuilder withSchemaBuilder(SchemaBuilder schemaBuilder) {
122 if (!isEmpty(schemaBuilder)) {
123 this.schemaBuilder = schemaBuilder;
124 }
125 return this;
126 }
127
128 @Override
129 public JaxbContextBuilder withDependenciesResolver(JaxbDependenciesResolver resolver) {
130 if ((isEmpty(dependenciesResolver) && !isEmpty(resolver))
131 || (!isEmpty(dependenciesResolver) && isEmpty(resolver))
132 || (!isEmpty(dependenciesResolver) && !ClassUtils.getUserClass(dependenciesResolver)
133 .equals(ClassUtils.getUserClass(resolver)))) {
134 clearCache();
135 }
136 this.dependenciesResolver = resolver;
137 return this;
138 }
139
140 @Override
141 public JaxbContextBuilder withContextClassLoader(ClassLoader classLoader) {
142 this.classLoader = classLoader;
143 return this;
144 }
145
146 @Override
147 public JaxbContextBuilder withFormattedOutput(boolean formattedOutput) {
148 this.formattedOutput = formattedOutput;
149 return this;
150 }
151
152 @Override
153 public JaxbContextBuilder withXmlAdapters(
154 Collection<? extends XmlAdapter<?, ?>> xmlAdapters) {
155
156 if (isEmpty(xmlAdapters)) {
157 this.xmlAdapters = null;
158 } else {
159 this.xmlAdapters = xmlAdapters
160 .stream()
161 .filter(Objects::nonNull)
162 .collect(Collectors.toList());
163 }
164 return this;
165 }
166
167 @Override
168 public JaxbContextBuilder withAttachmentMarshaller(
169 AttachmentMarshaller attachmentMarshaller) {
170 this.attachmentMarshaller = attachmentMarshaller;
171 return this;
172 }
173
174 @Override
175 public JaxbContextBuilder withAttachmentUnmarshaller(
176 AttachmentUnmarshaller attachmentUnmarshaller) {
177 this.attachmentUnmarshaller = attachmentUnmarshaller;
178 return this;
179 }
180
181 @Override
182 public JaxbContextBuilder withValidationEventHandler(
183 ValidationEventHandler validationEventHandler) {
184 this.validationEventHandler = validationEventHandler;
185 return this;
186 }
187
188 @Override
189 public JaxbContextBuilder add(JaxbContextMember data) {
190 return Optional.ofNullable(data)
191 .map(JaxbContextData::new)
192 .map(d -> {
193 clearCache();
194 jaxbContextDataMap.put(d.getKey(), d);
195 return this;
196 })
197 .orElse(this);
198 }
199
200
201 @Override
202 public Unmarshaller buildUnmarshaller(Class<?>... classes) {
203 if (!isEmpty(classes)) {
204 Class<?>[] jaxbClasses = isEmpty(dependenciesResolver)
205 ? classes
206 : dependenciesResolver.resolveClasses(classes);
207 Arrays.stream(jaxbClasses)
208 .map(JaxbContextData::new)
209 .forEach(data -> jaxbContextDataMap.computeIfAbsent(data.getKey(), key -> {
210 clearCache();
211 return data;
212 }));
213 }
214 JaxbContextWrapper jaxbContext = computeJaxbContext(null);
215 SchemaMode mode = jaxbContext.getSchemaMode();
216 if (mode == SchemaMode.ALWAYS
217 || mode == SchemaMode.UNMARSHAL
218 || mode == SchemaMode.EXTERNAL_XSD
219 && !isEmpty(jaxbContext.getDetails().getSchemaLocation())) {
220 jaxbContext.setSchema(computeSchema(jaxbContext));
221 }
222 try {
223 return jaxbContext.createUnmarshaller();
224
225 } catch (JAXBException e) {
226 throw new JaxbRuntimeException(e);
227 }
228 }
229
230 @Override
231 public Marshaller buildMarshaller(Object value) {
232 JaxbContextWrapper jaxbContext = computeJaxbContext(value);
233 SchemaMode mode = jaxbContext.getSchemaMode();
234 if ((mode == SchemaMode.ALWAYS
235 || mode == SchemaMode.MARSHAL
236 || mode == SchemaMode.EXTERNAL_XSD)
237 && !isEmpty(jaxbContext.getDetails().getSchemaLocation())) {
238 jaxbContext.setSchema(computeSchema(jaxbContext));
239 }
240 try {
241 return jaxbContext.createMarshaller();
242 } catch (JAXBException e) {
243 throw new JaxbRuntimeException(e);
244 }
245 }
246
247 @Override
248 public JaxbContextBuilder initJaxbContext() {
249 if (!jaxbContextDataMap.isEmpty()) {
250 buildJaxbContext(null);
251 }
252 return this;
253 }
254
255 @Override
256 public JaxbContextWrapper buildJaxbContext(Object value) {
257 JaxbContextWrapper wrapper = computeJaxbContext(value);
258 SchemaMode mode = wrapper.getSchemaMode();
259 if ((mode == SchemaMode.ALWAYS
260 || mode == SchemaMode.MARSHAL
261 || mode == SchemaMode.UNMARSHAL
262 || mode == SchemaMode.EXTERNAL_XSD)
263 && !isEmpty(wrapper.getDetails().getSchemaLocation())) {
264 wrapper.setSchema(computeSchema(wrapper));
265 }
266 return wrapper;
267 }
268
269 @Override
270 public Schema buildSchema(Object value) {
271 return computeSchema(value);
272 }
273
274 private JaxbContextDetails buildDetails() {
275 return jaxbContextDataMap.values().stream()
276 .collect(JaxbContextDetails.contextDataCollector());
277 }
278
279 private JaxbContextDetails buildDetails(Object value) {
280 if (isEmpty(value)) {
281 return buildDetails();
282 }
283 if (value instanceof Class<?>) {
284 return buildDetails(new Class<?>[]{(Class<?>) value});
285 }
286 Class<?>[] classes;
287 if (value instanceof Class<?>[]) {
288 classes = isEmpty(dependenciesResolver)
289 ? (Class<?>[]) value
290 : dependenciesResolver.resolveClasses(value);
291 } else {
292 classes = isEmpty(dependenciesResolver)
293 ? new Class<?>[]{value.getClass()}
294 : dependenciesResolver.resolveClasses(value);
295 }
296 return Arrays.stream(classes)
297 .map(JaxbContextData::new)
298 .map(data -> jaxbContextDataMap.computeIfAbsent(data.getKey(), key -> {
299 clearCache();
300 return data;
301 }))
302 .collect(JaxbContextDetails.contextDataCollector());
303 }
304
305 private JaxbContextWrapper computeJaxbContext(Object value) {
306 JaxbContextDetails details = buildDetails(value);
307 JAXBContext jaxbContext = jaxbContextCache.computeIfAbsent(details, key -> {
308 try {
309 return isEmpty(classLoader)
310 ? JAXBContext.newInstance(key.getClasses())
311 : JAXBContext.newInstance(key.getClasses(classLoader));
312
313 } catch (Exception e) {
314 throw new JaxbRuntimeException(
315 String.format("Creating jaxb context failed with builder context: %s", key),
316 e);
317 }
318 });
319 JaxbContextWrapper wrapper = new JaxbContextWrapper(jaxbContext, details);
320 wrapper.setAttachmentMarshaller(attachmentMarshaller);
321 wrapper.setAttachmentUnmarshaller(attachmentUnmarshaller);
322 wrapper.setFormattedOutput(formattedOutput);
323 wrapper.setValidationEventHandler(validationEventHandler);
324 wrapper.setXmlAdapters(xmlAdapters);
325 wrapper.setSchemaMode(schemaMode);
326 return wrapper;
327 }
328
329 private Schema computeSchema(Object value) {
330 return computeSchema(computeJaxbContext(value));
331 }
332
333 private Schema computeSchema(JaxbContextWrapper jaxbContext) {
334 JaxbContextDetails details = jaxbContext.getDetails();
335 return schemaCache.computeIfAbsent(details, key -> {
336 SchemaSourcesResolver sourcesResolver = new SchemaSourcesResolver();
337 try {
338 jaxbContext.generateSchema(sourcesResolver);
339 } catch (Exception e) {
340 throw new JaxbRuntimeException(e);
341 }
342 List<Source> sources = new ArrayList<>(
343 sourcesResolver.toSources(key.getNameSpaces()));
344 Set<String> locations = key.getSchemaLocations();
345 sources.addAll(schemaBuilder.fetchSchemaSources(locations));
346 return schemaBuilder.buildSchema(sources);
347 });
348 }
349
350 }