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