1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.oxm.jaxb;
18
19 import org.springframework.core.io.Resource;
20 import org.springframework.oxm.GenericMarshaller;
21 import org.springframework.oxm.GenericUnmarshaller;
22 import org.springframework.oxm.XmlMappingException;
23 import org.springframework.oxm.mime.MimeContainer;
24 import org.springframework.oxm.mime.MimeMarshaller;
25 import org.springframework.oxm.mime.MimeUnmarshaller;
26 import org.springframework.util.*;
27 import org.springframework.xml.transform.StaxResult;
28 import org.springframework.xml.transform.StaxSource;
29 import org.springframework.xml.validation.SchemaLoaderUtils;
30
31 import javax.activation.DataHandler;
32 import javax.activation.DataSource;
33 import javax.xml.XMLConstants;
34 import javax.xml.bind.*;
35 import javax.xml.bind.annotation.XmlRootElement;
36 import javax.xml.bind.annotation.XmlType;
37 import javax.xml.bind.annotation.adapters.XmlAdapter;
38 import javax.xml.bind.attachment.AttachmentMarshaller;
39 import javax.xml.bind.attachment.AttachmentUnmarshaller;
40 import javax.xml.datatype.Duration;
41 import javax.xml.datatype.XMLGregorianCalendar;
42 import javax.xml.namespace.QName;
43 import javax.xml.transform.Result;
44 import javax.xml.transform.Source;
45 import javax.xml.validation.Schema;
46 import java.awt.*;
47 import java.io.*;
48 import java.lang.reflect.GenericArrayType;
49 import java.lang.reflect.ParameterizedType;
50 import java.lang.reflect.Type;
51 import java.math.BigDecimal;
52 import java.math.BigInteger;
53 import java.net.URI;
54 import java.net.URISyntaxException;
55 import java.net.URLDecoder;
56 import java.net.URLEncoder;
57 import java.util.*;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class Jaxb2Marshaller extends AbstractJaxbMarshaller
80 implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller {
81
82 private Resource[] schemaResources;
83
84 private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
85
86 private Marshaller.Listener marshallerListener;
87
88 private Unmarshaller.Listener unmarshallerListener;
89
90 private XmlAdapter[] adapters;
91
92 private Schema schema;
93
94 private Class[] classesToBeBound;
95
96 private Map<String, ?> jaxbContextProperties;
97
98 private boolean mtomEnabled = false;
99
100
101
102
103
104 public void setAdapters(XmlAdapter[] adapters) {
105 this.adapters = adapters;
106 }
107
108
109
110
111
112
113
114 public void setClassesToBeBound(Class[] classesToBeBound) {
115 this.classesToBeBound = classesToBeBound;
116 }
117
118
119
120
121
122 public void setJaxbContextProperties(Map<String, ?> jaxbContextProperties) {
123 this.jaxbContextProperties = jaxbContextProperties;
124 }
125
126
127
128
129 public void setMarshallerListener(Marshaller.Listener marshallerListener) {
130 this.marshallerListener = marshallerListener;
131 }
132
133
134
135
136
137 public void setMtomEnabled(boolean mtomEnabled) {
138 this.mtomEnabled = mtomEnabled;
139 }
140
141
142
143
144
145
146
147 public void setSchemaLanguage(String schemaLanguage) {
148 this.schemaLanguage = schemaLanguage;
149 }
150
151
152
153
154 public void setSchema(Resource schemaResource) {
155 schemaResources = new Resource[]{schemaResource};
156 }
157
158
159
160
161 public void setSchemas(Resource[] schemaResources) {
162 this.schemaResources = schemaResources;
163 }
164
165
166
167
168 public void setUnmarshallerListener(Unmarshaller.Listener unmarshallerListener) {
169 this.unmarshallerListener = unmarshallerListener;
170 }
171
172 public boolean supports(Type type) {
173 if (type instanceof Class) {
174 return supportsInternal((Class) type, true);
175 } else if (type instanceof ParameterizedType) {
176 ParameterizedType parameterizedType = (ParameterizedType) type;
177 if (JAXBElement.class.equals(parameterizedType.getRawType())) {
178 Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1,
179 "Invalid amount of parameterized types in JAXBElement");
180 Type typeArgument = parameterizedType.getActualTypeArguments()[0];
181 if (typeArgument instanceof Class) {
182 Class clazz = (Class) typeArgument;
183 if (!isPrimitiveType(clazz) && !isStandardType(clazz) && !supportsInternal(clazz, false)) {
184 return false;
185 }
186 }
187 else if (typeArgument instanceof GenericArrayType) {
188 GenericArrayType genericArrayType = (GenericArrayType) typeArgument;
189 return genericArrayType.getGenericComponentType().equals(Byte.TYPE);
190 } else if (!supports(typeArgument)) {
191 return false;
192 }
193 return true;
194 }
195 }
196 return false;
197 }
198
199 private boolean isPrimitiveType(Class clazz) {
200 return (Boolean.class.equals(clazz) ||
201 Byte.class.equals(clazz) ||
202 Short.class.equals(clazz) ||
203 Integer.class.equals(clazz) ||
204 Long.class.equals(clazz) ||
205 Float.class.equals(clazz) ||
206 Double.class.equals(clazz) ||
207 byte[].class.equals(clazz));
208 }
209
210 private boolean isStandardType(Class clazz) {
211 return (String.class.equals(clazz) ||
212 BigInteger.class.equals(clazz) ||
213 BigDecimal.class.equals(clazz) ||
214 Calendar.class.isAssignableFrom(clazz) ||
215 Date.class.isAssignableFrom(clazz) ||
216 QName.class.equals(clazz) ||
217 URI.class.equals(clazz) ||
218 XMLGregorianCalendar.class.isAssignableFrom(clazz) ||
219 Duration.class.isAssignableFrom(clazz) ||
220 Object.class.equals(clazz) ||
221 Image.class.isAssignableFrom(clazz) ||
222 DataHandler.class.equals(clazz) ||
223 Source.class.isAssignableFrom(clazz) ||
224 UUID.class.equals(clazz));
225 }
226
227 public boolean supports(Class clazz) {
228 return supportsInternal(clazz, true);
229 }
230
231 private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
232 if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
233 return false;
234 }
235 if (clazz.getAnnotation(XmlType.class) == null) {
236 return false;
237 }
238 if (StringUtils.hasLength(getContextPath())) {
239 String className = ClassUtils.getQualifiedName(clazz);
240 int lastDotIndex = className.lastIndexOf('.');
241 if (lastDotIndex == -1) {
242 return false;
243 }
244 String packageName = className.substring(0, lastDotIndex);
245 String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
246 for (int i = 0; i < contextPaths.length; i++) {
247 if (contextPaths[i].equals(packageName)) {
248 return true;
249 }
250 }
251 return false;
252 } else if (!ObjectUtils.isEmpty(classesToBeBound)) {
253 return Arrays.asList(classesToBeBound).contains(clazz);
254 }
255 return false;
256 }
257
258
259
260
261
262 protected JAXBContext createJaxbContext() throws Exception {
263 if (JaxbUtils.getJaxbVersion() < JaxbUtils.JAXB_2) {
264 throw new IllegalStateException(
265 "Cannot use Jaxb2Marshaller in combination with JAXB 1.0. Use Jaxb1Marshaller instead.");
266 }
267 if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(classesToBeBound)) {
268 throw new IllegalArgumentException("specify either contextPath or classesToBeBound property; not both");
269 }
270 if (!ObjectUtils.isEmpty(schemaResources)) {
271 if (logger.isDebugEnabled()) {
272 logger.debug(
273 "Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(schemaResources));
274 }
275 schema = SchemaLoaderUtils.loadSchema(schemaResources, schemaLanguage);
276 }
277 if (StringUtils.hasLength(getContextPath())) {
278 return createJaxbContextFromContextPath();
279 } else if (!ObjectUtils.isEmpty(classesToBeBound)) {
280 return createJaxbContextFromClasses();
281 } else {
282 throw new IllegalArgumentException("setting either contextPath or classesToBeBound is required");
283 }
284 }
285
286 private JAXBContext createJaxbContextFromContextPath() throws JAXBException {
287 if (logger.isInfoEnabled()) {
288 logger.info("Creating JAXBContext with context path [" + getContextPath() + "]");
289 }
290 if (jaxbContextProperties != null) {
291 return JAXBContext
292 .newInstance(getContextPath(), ClassUtils.getDefaultClassLoader(), jaxbContextProperties);
293 } else {
294 return JAXBContext.newInstance(getContextPath());
295 }
296 }
297
298 private JAXBContext createJaxbContextFromClasses() throws JAXBException {
299 if (logger.isInfoEnabled()) {
300 logger.info("Creating JAXBContext with classes to be bound [" +
301 StringUtils.arrayToCommaDelimitedString(classesToBeBound) + "]");
302 }
303 if (jaxbContextProperties != null) {
304 return JAXBContext.newInstance(classesToBeBound, jaxbContextProperties);
305 } else {
306 return JAXBContext.newInstance(classesToBeBound);
307 }
308 }
309
310
311
312
313
314 protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
315 if (schema != null) {
316 marshaller.setSchema(schema);
317 }
318 if (marshallerListener != null) {
319 marshaller.setListener(marshallerListener);
320 }
321 if (adapters != null) {
322 for (int i = 0; i < adapters.length; i++) {
323 marshaller.setAdapter(adapters[i]);
324 }
325 }
326 }
327
328 protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException {
329 if (schema != null) {
330 unmarshaller.setSchema(schema);
331 }
332 if (unmarshallerListener != null) {
333 unmarshaller.setListener(unmarshallerListener);
334 }
335 if (adapters != null) {
336 for (int i = 0; i < adapters.length; i++) {
337 unmarshaller.setAdapter(adapters[i]);
338 }
339 }
340 }
341
342
343
344
345
346 public void marshal(Object graph, Result result) throws XmlMappingException {
347 marshal(graph, result, null);
348 }
349
350 public void marshal(Object graph, Result result, MimeContainer mimeContainer) throws XmlMappingException {
351 try {
352 Marshaller marshaller = createMarshaller();
353 if (mtomEnabled && mimeContainer != null) {
354 marshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer));
355 }
356 if (result instanceof StaxResult) {
357 marshalStaxResult(marshaller, graph, (StaxResult) result);
358 } else {
359 marshaller.marshal(graph, result);
360 }
361 }
362 catch (JAXBException ex) {
363 throw convertJaxbException(ex);
364 }
365 }
366
367 private void marshalStaxResult(Marshaller jaxbMarshaller, Object graph, StaxResult staxResult)
368 throws JAXBException {
369 if (staxResult.getXMLStreamWriter() != null) {
370 jaxbMarshaller.marshal(graph, staxResult.getXMLStreamWriter());
371 } else if (staxResult.getXMLEventWriter() != null) {
372 jaxbMarshaller.marshal(graph, staxResult.getXMLEventWriter());
373 } else {
374 throw new IllegalArgumentException("StaxResult contains neither XMLStreamWriter nor XMLEventConsumer");
375 }
376 }
377
378
379
380
381
382 public Object unmarshal(Source source) throws XmlMappingException {
383 return unmarshal(source, null);
384 }
385
386 public Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMappingException {
387 try {
388 Unmarshaller unmarshaller = createUnmarshaller();
389 if (mtomEnabled && mimeContainer != null) {
390 unmarshaller.setAttachmentUnmarshaller(new Jaxb2AttachmentUnmarshaller(mimeContainer));
391 }
392 if (source instanceof StaxSource) {
393 return unmarshalStaxSource(unmarshaller, (StaxSource) source);
394 } else {
395 return unmarshaller.unmarshal(source);
396 }
397 }
398 catch (JAXBException ex) {
399 throw convertJaxbException(ex);
400 }
401 }
402
403 private Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, StaxSource staxSource) throws JAXBException {
404 if (staxSource.getXMLStreamReader() != null) {
405 return jaxbUnmarshaller.unmarshal(staxSource.getXMLStreamReader());
406 } else if (staxSource.getXMLEventReader() != null) {
407 return jaxbUnmarshaller.unmarshal(staxSource.getXMLEventReader());
408 } else {
409 throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader");
410 }
411 }
412
413
414
415
416
417 private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller {
418
419 private final MimeContainer mimeContainer;
420
421 public Jaxb2AttachmentMarshaller(MimeContainer mimeContainer) {
422 this.mimeContainer = mimeContainer;
423 }
424
425 public String addMtomAttachment(byte[] data,
426 int offset,
427 int length,
428 String mimeType,
429 String elementNamespace,
430 String elementLocalName) {
431 ByteArrayDataSource dataSource = new ByteArrayDataSource(mimeType, data, offset, length);
432 return addMtomAttachment(new DataHandler(dataSource), elementNamespace, elementLocalName);
433 }
434
435 public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) {
436 String host = getHost(elementNamespace, dataHandler);
437 String contentId = UUID.randomUUID() + "@" + host;
438 mimeContainer.addAttachment("<" + contentId + ">", dataHandler);
439 try {
440 contentId = URLEncoder.encode(contentId, "UTF-8");
441 }
442 catch (UnsupportedEncodingException e) {
443
444 }
445 return "cid:" + contentId;
446 }
447
448 private String getHost(String elementNamespace, DataHandler dataHandler) {
449 try {
450 URI uri = new URI(elementNamespace);
451 return uri.getHost();
452 }
453 catch (URISyntaxException e) {
454
455 }
456 return dataHandler.getName();
457 }
458
459 public String addSwaRefAttachment(DataHandler dataHandler) {
460 String contentId = UUID.randomUUID() + "@" + dataHandler.getName();
461 mimeContainer.addAttachment(contentId, dataHandler);
462 return contentId;
463 }
464
465 @Override
466 public boolean isXOPPackage() {
467 return mimeContainer.convertToXopPackage();
468 }
469 }
470
471 private static class Jaxb2AttachmentUnmarshaller extends AttachmentUnmarshaller {
472
473 private final MimeContainer mimeContainer;
474
475 public Jaxb2AttachmentUnmarshaller(MimeContainer mimeContainer) {
476 this.mimeContainer = mimeContainer;
477 }
478
479 public byte[] getAttachmentAsByteArray(String cid) {
480 try {
481 DataHandler dataHandler = getAttachmentAsDataHandler(cid);
482 return FileCopyUtils.copyToByteArray(dataHandler.getInputStream());
483 }
484 catch (IOException ex) {
485 throw new JaxbUnmarshallingFailureException(ex);
486 }
487 }
488
489 public DataHandler getAttachmentAsDataHandler(String contentId) {
490 if (contentId.startsWith("cid:")) {
491 contentId = contentId.substring("cid:".length());
492 try {
493 contentId = URLDecoder.decode(contentId, "UTF-8");
494 }
495 catch (UnsupportedEncodingException e) {
496
497 }
498 contentId = '<' + contentId + '>';
499 }
500 return mimeContainer.getAttachment(contentId);
501 }
502
503 @Override
504 public boolean isXOPPackage() {
505 return mimeContainer.isXopPackage();
506 }
507 }
508
509
510
511
512 private static class ByteArrayDataSource implements DataSource {
513
514 private byte[] data;
515
516 private String contentType;
517
518 private int offset;
519
520 private int length;
521
522 public ByteArrayDataSource(String contentType, byte[] data, int offset, int length) {
523 this.contentType = contentType;
524 this.data = data;
525 this.offset = offset;
526 this.length = length;
527 }
528
529 public InputStream getInputStream() throws IOException {
530 return new ByteArrayInputStream(data, offset, length);
531 }
532
533 public OutputStream getOutputStream() throws IOException {
534 throw new UnsupportedOperationException();
535 }
536
537 public String getContentType() {
538 return contentType;
539 }
540
541 public String getName() {
542 return "ByteArrayDataSource";
543 }
544 }
545
546 }
547