1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.common.util.xml.jaxb;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30
31 import javax.xml.bind.JAXBContext;
32 import javax.xml.bind.JAXBException;
33 import javax.xml.bind.Marshaller;
34 import javax.xml.bind.Unmarshaller;
35 import javax.xml.bind.UnmarshallerHandler;
36 import javax.xml.parsers.ParserConfigurationException;
37 import javax.xml.parsers.SAXParser;
38 import javax.xml.parsers.SAXParserFactory;
39
40 import org.apache.commons.io.FileUtils;
41 import org.apache.commons.io.IOUtils;
42 import org.kuali.common.util.Assert;
43 import org.kuali.common.util.CollectionUtils;
44 import org.kuali.common.util.LocationUtils;
45 import org.kuali.common.util.xml.service.XmlService;
46 import org.xml.sax.InputSource;
47 import org.xml.sax.SAXException;
48 import org.xml.sax.XMLReader;
49
50 public class JAXBXmlService implements XmlService {
51
52 private final boolean formatOutput;
53 private final boolean useNamespaceAwareParser;
54 private final Map<String, ?> properties;
55 private final boolean useEclipseLinkMoxyProvider;
56
57 @Override
58 public void write(File file, Object object) {
59 Assert.noNulls(file, object);
60 OutputStream out = null;
61 try {
62 out = FileUtils.openOutputStream(file);
63 write(out, object);
64 } catch (IOException e) {
65 throw new IllegalStateException("Unexpected IO error", e);
66 } finally {
67 IOUtils.closeQuietly(out);
68 }
69 }
70
71 @Override
72 public void write(OutputStream out, Object object) {
73 Assert.noNulls(out, object);
74 try {
75 JAXBContext context = getJAXBContext(object.getClass());
76 Marshaller marshaller = context.createMarshaller();
77 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formatOutput);
78 marshaller.marshal(object, out);
79 } catch (JAXBException e) {
80 throw new IllegalStateException("Unexpected JAXB error", e);
81 }
82 }
83
84 @Override
85 public <T> T getObjectFromXml(String xml, String encoding, Class<T> type) {
86 Assert.noBlanks(xml, encoding);
87 Assert.noNulls(type);
88 InputStream in = null;
89 try {
90 in = new ByteArrayInputStream(xml.getBytes(encoding));
91 return getObject(in, type);
92 } catch (IOException e) {
93 throw new IllegalStateException("Unexpected IO error", e);
94 } finally {
95 IOUtils.closeQuietly(in);
96 }
97 }
98
99 @Override
100 @SuppressWarnings("unchecked")
101 public <T> T getObject(InputStream in, Class<T> type) {
102 Assert.noNulls(in, type);
103 try {
104 Unmarshaller unmarshaller = getUnmarshaller(type);
105 if (useNamespaceAwareParser) {
106 return (T) unmarshaller.unmarshal(in);
107 } else {
108 UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
109 SAXParserFactory spf = SAXParserFactory.newInstance();
110 SAXParser sp = spf.newSAXParser();
111 XMLReader xr = sp.getXMLReader();
112 xr.setContentHandler(unmarshallerHandler);
113 InputSource xmlSource = new InputSource(in);
114 xr.parse(xmlSource);
115 return (T) unmarshallerHandler.getResult();
116 }
117 } catch (JAXBException e) {
118 throw new IllegalStateException("Unexpected JAXB error", e);
119 } catch (SAXException e) {
120 throw new IllegalStateException("Unexpected SAX error", e);
121 } catch (IOException e) {
122 throw new IllegalStateException("Unexpected IO error", e);
123 } catch (ParserConfigurationException e) {
124 throw new IllegalStateException("Unexpected parser configuration error", e);
125 }
126 }
127
128 @Override
129 public <T> T getObject(File file, Class<T> type) {
130 Assert.exists(file);
131 Assert.noNulls(type);
132 return getObject(LocationUtils.getCanonicalPath(file), type);
133 }
134
135 @Override
136 public <T> T getObject(String location, Class<T> type) {
137 Assert.noBlanks(location);
138 Assert.noNulls(type);
139 InputStream in = null;
140 try {
141 in = LocationUtils.getInputStream(location);
142 return getObject(in, type);
143 } catch (IOException e) {
144 throw new IllegalStateException("Unexpected IO error", e);
145 } finally {
146 IOUtils.closeQuietly(in);
147 }
148 }
149
150 @Override
151 public String toXml(Object object, String encoding) {
152 Assert.noNulls(object);
153 Assert.noBlanks(encoding);
154 ByteArrayOutputStream out = new ByteArrayOutputStream();
155 write(out, object);
156 try {
157 return out.toString(encoding);
158 } catch (UnsupportedEncodingException e) {
159 throw new IllegalArgumentException(e);
160 }
161 }
162
163
164
165
166 @Override
167 @Deprecated
168 public String toString(Object object, String encoding) {
169 return toXml(object, encoding);
170 }
171
172 protected Unmarshaller getUnmarshaller(Class<?> clazz) throws JAXBException {
173 Class<?>[] classes = getClassesToBeBound(clazz);
174 JAXBContext jc = JAXBContext.newInstance(classes);
175 return jc.createUnmarshaller();
176 }
177
178 protected JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException {
179 Class<?>[] classes = getClassesToBeBound(clazz);
180 if (properties.size() == 0) {
181 return JAXBContext.newInstance(classes);
182 } else {
183 return JAXBContext.newInstance(classes, properties);
184 }
185 }
186
187 protected Class<?>[] getClassesToBeBound(Class<?> clazz) {
188 List<Class<?>> classes = getClassesList(clazz);
189
190 if (useEclipseLinkMoxyProvider) {
191 classes.add(0, UseEclipseLinkMoxyProvider.class);
192 }
193 return classes.toArray(new Class<?>[classes.size()]);
194 }
195
196 protected List<Class<?>> getClassesList(Class<?> clazz) {
197 XmlClassBindings bindings = clazz.getAnnotation(XmlClassBindings.class);
198 List<Class<?>> classes = new ArrayList<Class<?>>(CollectionUtils.singletonList(clazz));
199 if (bindings == null) {
200
201 return classes;
202 } else {
203
204 for (Class<?> binding : bindings.classes()) {
205 classes.addAll(getClassesList(binding));
206 }
207 return classes;
208 }
209 }
210
211 public boolean isFormatOutput() {
212 return formatOutput;
213 }
214
215 public boolean isUseNamespaceAwareParser() {
216 return useNamespaceAwareParser;
217 }
218
219 public static class Builder {
220
221 private static final Map<String, ?> EMPTY_MAP = Collections.unmodifiableMap(new HashMap<String, Object>());
222
223 public static final boolean FORMAT_OUTPUT = true;
224 public static final boolean USE_NAMESPACE_AWARE_PARSER = true;
225 public static final boolean USE_ECLIPSE_LINK_MOXY_PROVIDER = true;
226
227
228 private boolean formatOutput = FORMAT_OUTPUT;
229 private Map<String, ?> properties = EMPTY_MAP;
230
231
232
233 private boolean useNamespaceAwareParser = USE_NAMESPACE_AWARE_PARSER;
234
235
236
237
238
239
240
241
242
243
244 private boolean useEclipseLinkMoxyProvider = USE_ECLIPSE_LINK_MOXY_PROVIDER;
245
246 public Builder useEclipseLinkMoxyProvider(boolean useEclipseLinkMoxyProvider) {
247 this.useEclipseLinkMoxyProvider = useEclipseLinkMoxyProvider;
248 return this;
249 }
250
251 public Builder formatOutput(boolean formatOutput) {
252 this.formatOutput = formatOutput;
253 return this;
254 }
255
256 public Builder useNamespaceAwareParser(boolean useNamespaceAwareParser) {
257 this.useNamespaceAwareParser = useNamespaceAwareParser;
258 return this;
259 }
260
261 public Builder properties(Map<String, ?> properties) {
262 this.properties = properties;
263 return this;
264 }
265
266 public JAXBXmlService build() {
267 Assert.noNulls(properties);
268 this.properties = Collections.unmodifiableMap(new HashMap<String, Object>(properties));
269 return new JAXBXmlService(this);
270 }
271
272 }
273
274 private JAXBXmlService(Builder builder) {
275 this.formatOutput = builder.formatOutput;
276 this.useNamespaceAwareParser = builder.useNamespaceAwareParser;
277 this.useEclipseLinkMoxyProvider = builder.useEclipseLinkMoxyProvider;
278 this.properties = builder.properties;
279 }
280
281 public Map<String, ?> getProperties() {
282 return properties;
283 }
284
285 public boolean isUseEclipseLinkMoxyProvider() {
286 return useEclipseLinkMoxyProvider;
287 }
288
289 private static class UseEclipseLinkMoxyProvider {
290
291
292
293
294
295
296
297
298
299
300
301
302 }
303
304 }