1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jadira.bindings.core.loader;
17
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.net.URL;
25 import java.net.URLConnection;
26 import java.util.ArrayList;
27 import java.util.List;
28
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import javax.xml.parsers.ParserConfigurationException;
32
33 import org.jadira.bindings.core.annotation.BindingScope;
34 import org.jadira.bindings.core.annotation.DefaultBinding;
35 import org.jadira.bindings.core.spi.ConverterProvider;
36 import org.jadira.bindings.core.utils.lang.IterableNodeList;
37 import org.jadira.bindings.core.utils.reflection.ClassLoaderUtils;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.Node;
41 import org.xml.sax.ErrorHandler;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44 import org.xml.sax.SAXParseException;
45
46
47
48
49
50 public final class BindingXmlLoader {
51
52 private static final String BINDINGS_NAMESPACE = "http://org.jadira.bindings/xml/ns/binding";
53
54 private BindingXmlLoader() {
55 }
56
57
58
59
60
61
62
63 public static BindingConfiguration load(URL location) throws IllegalStateException {
64
65 Document doc;
66 try {
67 doc = loadDocument(location);
68 } catch (IOException e) {
69 throw new IllegalStateException("Cannot load " + location.toExternalForm(), e);
70 } catch (ParserConfigurationException e) {
71 throw new IllegalStateException("Cannot initialise parser for " + location.toExternalForm(), e);
72 } catch (SAXException e) {
73 throw new IllegalStateException("Cannot parse " + location.toExternalForm(), e);
74 }
75 BindingConfiguration configuration = parseDocument(doc);
76 return configuration;
77 }
78
79
80
81
82
83
84
85
86
87 private static Document loadDocument(URL location) throws IOException, ParserConfigurationException, SAXException {
88
89 InputStream inputStream = null;
90
91 if (location != null) {
92 URLConnection urlConnection = location.openConnection();
93 urlConnection.setUseCaches(false);
94 inputStream = urlConnection.getInputStream();
95 }
96 if (inputStream == null) {
97 if (location == null) {
98 throw new IOException("Failed to obtain InputStream for named location: null");
99 } else {
100 throw new IOException("Failed to obtain InputStream for named location: " + location.toExternalForm());
101 }
102 }
103
104 InputSource inputSource = new InputSource(inputStream);
105
106 List<SAXParseException> errors = new ArrayList<SAXParseException>();
107 DocumentBuilder docBuilder = constructDocumentBuilder(errors);
108
109 Document document = docBuilder.parse(inputSource);
110 if (!errors.isEmpty()) {
111 if (location == null) {
112 throw new IllegalStateException("Invalid File: null", (Throwable) errors.get(0));
113 } else {
114 throw new IllegalStateException("Invalid file: " + location.toExternalForm(), (Throwable) errors.get(0));
115 }
116 }
117 return document;
118 }
119
120
121
122
123
124
125
126 private static DocumentBuilder constructDocumentBuilder(List<SAXParseException> errors)
127 throws ParserConfigurationException {
128
129 DocumentBuilderFactory documentBuilderFactory = constructDocumentBuilderFactory();
130 DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
131 docBuilder.setEntityResolver(new BindingXmlEntityResolver());
132 docBuilder.setErrorHandler(new BindingXmlErrorHandler(errors));
133 return docBuilder;
134 }
135
136
137
138
139
140 private static DocumentBuilderFactory constructDocumentBuilderFactory() {
141
142 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
143
144 documentBuilderFactory.setValidating(true);
145 documentBuilderFactory.setNamespaceAware(true);
146
147 try {
148 documentBuilderFactory.setAttribute("http://apache.org/xml/features/validation/schema", true);
149 } catch (IllegalArgumentException e) {
150
151 }
152 documentBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
153 "http://www.w3.org/2001/XMLSchema");
154 documentBuilderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
155 "classpath:/jadira-bindings.xsd");
156 return documentBuilderFactory;
157 }
158
159
160
161
162
163
164 private static BindingConfiguration parseDocument(Document doc) {
165
166 BindingConfiguration result = new BindingConfiguration();
167
168 Element docRoot = doc.getDocumentElement();
169 for (Node next : new IterableNodeList(docRoot.getChildNodes())) {
170 if (Node.ELEMENT_NODE == next.getNodeType()) {
171 Element element = (Element) next;
172
173 if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
174 && "provider".equals(element.getLocalName())) {
175
176 Provider provider = parseProviderElement(element);
177 result.addProvider(provider);
178 }
179
180 if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
181 && "extension".equals(element.getLocalName())) {
182
183 Extension<?> extension = parseBinderExtensionElement(element);
184 result.addExtension(extension);
185 }
186
187 if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
188 && "binding".equals(element.getLocalName())) {
189
190 BindingConfigurationEntry binding = parseBindingConfigurationEntryElement(element);
191 result.addBindingEntry(binding);
192 }
193 }
194 }
195 return result;
196 }
197
198
199
200
201
202
203 private static Provider parseProviderElement(Element element) {
204
205 Class<?> providerClass = lookupClass(element.getAttribute("class"));
206
207 if (providerClass == null) {
208 throw new IllegalStateException("Referenced class {" + element.getAttribute("class")
209 + "} could not be found");
210 }
211 if (!ConverterProvider.class.isAssignableFrom(providerClass)) {
212 throw new IllegalStateException("Referenced class {" + element.getAttribute("class")
213 + "} did not implement BindingProvider");
214 }
215
216 @SuppressWarnings("unchecked")
217 final Class<ConverterProvider> typedProviderClass = (Class<ConverterProvider>) providerClass;
218 return new Provider((Class<ConverterProvider>) typedProviderClass);
219 }
220
221
222
223
224
225
226 private static <T> Extension<T> parseBinderExtensionElement(Element element) {
227
228 @SuppressWarnings("unchecked")
229 Class<T> providerClass = (Class<T>)lookupClass(element.getAttribute("class"));
230
231 Class<?> implementationClass = lookupClass(element.getAttribute("implementationClass"));
232
233 if (providerClass == null) {
234 throw new IllegalStateException("Referenced class {" + element.getAttribute("class")
235 + "} could not be found");
236 }
237 if (implementationClass == null) {
238 throw new IllegalStateException("Referenced implementation class {" + element.getAttribute("implementationClass")
239 + "} could not be found");
240 }
241 if (providerClass.isAssignableFrom(implementationClass)) {
242 throw new IllegalStateException("Referenced class {" + element.getAttribute("class")
243 + "} did not implement BindingProvider");
244 }
245
246 try {
247 @SuppressWarnings("unchecked")
248 final Class<? extends T> myImplementationClass = (Class<T>) implementationClass.newInstance();
249 return new Extension<T>(providerClass, myImplementationClass);
250 } catch (InstantiationException e) {
251 throw new IllegalStateException("Referenced implementation class {" + element.getAttribute("implementationClass")
252 + "} could not be instantiated");
253 } catch (IllegalAccessException e) {
254 throw new IllegalStateException("Referenced implementation class {" + element.getAttribute("implementationClass")
255 + "} could not be accessed");
256 }
257 }
258
259
260
261
262
263
264 @SuppressWarnings("unchecked")
265 private static BindingConfigurationEntry parseBindingConfigurationEntryElement(Element element) {
266
267 Class<?> bindingClass = null;
268 Class<?> sourceClass = null;
269 Class<?> targetClass = null;
270 Method toMethod = null;
271 Method fromMethod = null;
272 Constructor<?> fromConstructor = null;
273 Class<? extends Annotation> qualifier = DefaultBinding.class;
274
275 if (element.getAttribute("class").length() > 0) {
276 bindingClass = lookupClass(element.getAttribute("class"));
277 }
278
279 if (element.getAttribute("sourceClass").length() > 0) {
280 sourceClass = lookupClass(element.getAttribute("sourceClass"));
281 }
282 if (element.getAttribute("targetClass").length() > 0) {
283 targetClass = lookupClass(element.getAttribute("targetClass"));
284 }
285
286 if (element.getAttribute("qualifier").length() > 0) {
287
288 qualifier = (Class<? extends Annotation>) lookupClass(element.getAttribute("qualifier"));
289
290 if (qualifier.getAnnotation(BindingScope.class) == null) {
291 throw new IllegalStateException("Qualifier class {" + element.getAttribute("qualifier")
292 + "} was not marked as BindingScope");
293 }
294 }
295
296 if (bindingClass != null) {
297 for (Node next : new IterableNodeList(element.getChildNodes())) {
298 if (Node.ELEMENT_NODE == next.getNodeType()) {
299 Element childElement = (Element) next;
300 if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
301 && "toMethod".equals(element.getLocalName())) {
302
303 String toMethodName = childElement.getTextContent();
304
305 try {
306 toMethod = bindingClass.getMethod(toMethodName, new Class[] { targetClass });
307 } catch (SecurityException e) {
308 } catch (NoSuchMethodException e) {
309 }
310 if (toMethod != null && (!String.class.equals(toMethod.getReturnType())
311 || !Modifier.isStatic(toMethod.getModifiers()))) {
312 toMethod = null;
313 }
314 if (toMethod == null && bindingClass.equals(targetClass)) {
315 try {
316 toMethod = bindingClass.getMethod(toMethodName, new Class[] {});
317 } catch (SecurityException e) {
318 } catch (NoSuchMethodException e) {
319 }
320 if (toMethod != null && Modifier.isStatic(toMethod.getModifiers())) {
321 toMethod = null;
322 }
323 }
324
325 } else if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
326 && "fromMethod".equals(element.getLocalName())) {
327
328 String fromMethodName = childElement.getTextContent();
329
330 try {
331 fromMethod = bindingClass.getMethod(fromMethodName, new Class[] { String.class });
332 } catch (SecurityException e) {
333 } catch (NoSuchMethodException e) {
334 }
335 if (fromMethod != null && ((targetClass != null && !targetClass.isAssignableFrom(fromMethod.getReturnType()))
336 || !Modifier.isStatic(fromMethod.getModifiers()))) {
337 fromMethod = null;
338 }
339
340 } else if (BINDINGS_NAMESPACE.equals(element.getNamespaceURI())
341 && "fromConstructor".equals(element.getLocalName())) {
342
343 try {
344 fromConstructor = bindingClass.getConstructor(new Class[] { String.class });
345 } catch (SecurityException e) {
346 } catch (NoSuchMethodException e) {
347 }
348 }
349 }
350 }
351 }
352
353 if (bindingClass == null) {
354
355 if (sourceClass == null) {
356 throw new IllegalStateException("If bindingClass is not populated, sourceClass must be present");
357 }
358 if (targetClass == null) {
359 throw new IllegalStateException("If bindingClass is not populated, targetClass must be present");
360 }
361 if (fromMethod != null && fromConstructor != null) {
362 throw new IllegalStateException("If fromMethod is populated, fromConstructor must not be present");
363 }
364
365 if (fromMethod == null) {
366
367 return new BindingConfigurationEntry(sourceClass, targetClass, qualifier, toMethod, fromConstructor);
368 } else {
369
370 return new BindingConfigurationEntry(sourceClass, targetClass, qualifier, toMethod, fromMethod);
371 }
372 } else {
373 if (sourceClass != null) {
374 throw new IllegalStateException("If bindingClass is populated, sourceClass must not be present");
375 }
376 if (targetClass != null) {
377 throw new IllegalStateException("If bindingClass is populated, targetClass must not be present");
378 }
379 if (toMethod != null) {
380 throw new IllegalStateException("If bindingClass is populated, toMethod must not be present");
381 }
382 if (fromMethod != null) {
383 throw new IllegalStateException("If bindingClass is populated, fromMethod must not be present");
384 }
385 if (fromConstructor != null) {
386 throw new IllegalStateException("If bindingClass is populated, fromConstructor must not be present");
387 }
388
389 return new BindingConfigurationEntry(bindingClass, qualifier);
390 }
391 }
392
393
394
395
396
397
398 private static Class<?> lookupClass(String elementName) {
399
400 Class<?> clazz = null;
401 try {
402 clazz = ClassLoaderUtils.getClassLoader().loadClass(elementName);
403 } catch (ClassNotFoundException e) {
404 return null;
405 }
406
407 return clazz;
408 }
409
410
411
412
413 private static class BindingXmlErrorHandler implements ErrorHandler {
414
415 private List<SAXParseException> errors;
416
417
418
419
420
421 BindingXmlErrorHandler(List<SAXParseException> errors) {
422 this.errors = errors;
423 }
424
425
426
427
428
429 public void error(SAXParseException error) {
430 errors.add(error);
431 }
432
433
434
435
436
437 public void fatalError(SAXParseException error) {
438 errors.add(error);
439 }
440
441
442
443
444
445 public void warning(SAXParseException warn) {
446
447 }
448 }
449 }