Finish exception handling in AdapterRegistry2
[simantics/platform.git] / bundles / org.simantics.db.services / src / org / simantics / db / services / adaption / AdapterRegistry2.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.db.services.adaption;
13
14 import java.io.File;
15 import java.io.StringReader;
16 import java.net.URL;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.Future;
23
24 import javax.xml.parsers.DocumentBuilder;
25 import javax.xml.parsers.DocumentBuilderFactory;
26
27 import org.eclipse.core.runtime.Path;
28 import org.eclipse.core.runtime.Platform;
29 import org.osgi.framework.Bundle;
30 import org.osgi.framework.BundleContext;
31 import org.simantics.db.ReadGraph;
32 import org.simantics.db.Resource;
33 import org.simantics.db.Session;
34 import org.simantics.db.adaption.Adapter;
35 import org.simantics.db.adaption.AdapterInstaller;
36 import org.simantics.db.adaption.AdaptionService;
37 import org.simantics.db.exception.DatabaseException;
38 import org.simantics.db.request.Read;
39 import org.simantics.db.services.adaption.reflection.AdaptingDynamicAdapter2;
40 import org.simantics.db.services.adaption.reflection.AtMostOneRelatedResource2;
41 import org.simantics.db.services.adaption.reflection.ConstantAdapter;
42 import org.simantics.db.services.adaption.reflection.GraphObject2;
43 import org.simantics.db.services.adaption.reflection.IDynamicAdapter2;
44 import org.simantics.db.services.adaption.reflection.OrderedSetResources2;
45 import org.simantics.db.services.adaption.reflection.ReflectionAdapter2;
46 import org.simantics.db.services.adaption.reflection.RelatedResources2;
47 import org.simantics.db.services.adaption.reflection.SingleRelatedResource2;
48 import org.simantics.db.services.adaption.reflection.StaticMethodAdapter;
49 import org.simantics.db.services.adaption.reflection.ThisResource2;
50 import org.simantics.scl.reflection.OntologyVersions;
51 import org.simantics.utils.FileUtils;
52 import org.simantics.utils.threads.ThreadUtils;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.w3c.dom.DOMException;
56 import org.w3c.dom.Document;
57 import org.w3c.dom.NamedNodeMap;
58 import org.w3c.dom.Node;
59 import org.w3c.dom.NodeList;
60 import org.xml.sax.ErrorHandler;
61 import org.xml.sax.InputSource;
62 import org.xml.sax.SAXException;
63 import org.xml.sax.SAXParseException;
64
65 public class AdapterRegistry2 {
66
67     private static final Logger LOGGER = LoggerFactory.getLogger(AdapterRegistry2.class);
68
69     public static final String ADAPTERS_FILE = "adapters.xml";
70
71     public static final String ADAPTERS = "adapters";
72     public static final String ADAPTER = "adapter";
73     public static final String TARGET = "target";
74     public static final String BASE_TYPE = "baseType";
75     public static final String TYPE = "type";
76     public static final String RESOURCE = "resource";
77     public static final String URI = "uri";
78     public static final String INTERFACE = "interface";
79     public static final String CLASS = "class";
80     public static final String ADAPTER_CLASS = "adapterClass";
81     public static final String CONTEXT_CLASS = "contextClass";
82     public static final String INSTALLER = "installer";
83     public static final String CONSTRUCTOR = "constructor";
84
85     static private AdapterRegistry2 instance = new AdapterRegistry2();
86     ConcurrentHashMap<AdapterInstaller, String> installerSources = new ConcurrentHashMap<>();
87     Collection<Exception> exceptions = new ArrayList<Exception>();
88
89     public static AdapterRegistry2 getInstance() {
90         return instance;
91     }
92
93     private void addInstaller(AdapterInstaller installer, String sourceDesc) {
94         installerSources.put(installer, sourceDesc);
95     }
96
97     private static void handleException(Exception e, String fileName) {
98         LOGGER.error("At {}", fileName, e);
99     }
100
101     private void handleException(Exception e, AdapterInstaller installer) {
102         String desc = installerSources.get(installer);
103         LOGGER.error("At {}, installer {}", desc, installer, e);
104     }
105
106     private void handleAdaptersDocument(Loader b, Document doc, String fileName) {
107         try {
108             Node node = doc.getDocumentElement();
109             if(node.getNodeName().equals(ADAPTERS)) {
110                 NodeList nodeList = node.getChildNodes();
111                 for(int i=0;i<nodeList.getLength();++i) {
112                     Node n = nodeList.item(i);
113                     if(n.getNodeName().equals(TARGET))
114                         handleTarget(b, n, fileName);
115                     else if(n.getNodeName().equals(INSTALLER))
116                         handleInstaller(b, n, fileName);
117                 }
118             }
119         } catch (Exception e) {
120             handleException(e, fileName);
121         }
122     }
123
124     private void handleTarget(Loader b, Node node, String fileName) {
125         try {
126             Class<?> interface_ =
127                 b.loadClass(node.getAttributes().getNamedItem("interface")
128                         .getNodeValue());
129             NodeList nodeList = node.getChildNodes();
130             for(int i=0;i<nodeList.getLength();++i) {
131                 Node n = nodeList.item(i);
132                 String nodeName = n.getNodeName();
133                 if(nodeName.equals(BASE_TYPE))
134                     handleBaseType(b, interface_, n, fileName);
135                 else if(nodeName.equals(TYPE))
136                     handleType(b, interface_, n, fileName);
137                 else if(nodeName.equals(ADAPTER))
138                     handleAdapter(b, interface_, n, fileName);
139                 else if(nodeName.equals(RESOURCE))
140                     handleResource(b, interface_, n, fileName);
141             }
142         } catch (Exception e) {
143             handleException(e, fileName);
144         }
145     }
146
147     private void handleInstaller(Loader b, Node node, String fileName) {
148         try {
149             AdapterInstaller installer =
150                 ((Class<?>)b.loadClass(node.getAttributes().getNamedItem("class").getNodeValue()))
151                 .asSubclass(AdapterInstaller.class).newInstance();
152             addInstaller(installer, fileName);
153         } catch (Exception e) {
154             handleException(e, fileName);
155         }
156     }
157
158     private <T> void handleResource(final Loader b, final Class<T> interface_, final Node node, String fileName) {
159         try {
160             NamedNodeMap attr = node.getAttributes();
161             final String uri = attr.getNamedItem(URI).getNodeValue();
162             final String className = attr.getNamedItem(CLASS).getNodeValue();
163             Node constructorNode = attr.getNamedItem(CONSTRUCTOR);
164             final String constructor = constructorNode == null ? null : constructorNode.getNodeValue();
165 //            System.out.println("AdapterRegistry2.handleResource: " + b + " " + uri + " " + interface_);
166             addInstaller(
167
168                     new AdapterInstaller() {
169
170                         @Override
171                         public void install(ReadGraph g, AdaptionService service) throws Exception {
172                             Class<? extends T> clazz = b.loadClass(className).asSubclass(interface_);
173                             List<IDynamicAdapter2> parameters = readParameters(g, node, b);
174                             IDynamicAdapter2[] parameterArray = 
175                                 parameters.toArray(new IDynamicAdapter2[parameters.size()]);
176                             Resource r = g.getResource(uri);
177                             service.addInstanceAdapter(
178                                     r,
179                                     interface_,
180                                     constructor == null 
181                                     ? new ReflectionAdapter2<T>(clazz, parameterArray)
182                                     : new StaticMethodAdapter<T>(clazz, constructor, parameterArray));
183                         }
184
185                     }, fileName);
186         } catch (Exception e) {
187             handleException(e, fileName);
188         }
189     }
190
191     private <T> void handleType(final Loader b, final Class<T> interface_, final Node node, String fileName) {
192         try {
193             final NamedNodeMap attr = node.getAttributes();
194             final String uri = attr.getNamedItem(URI).getNodeValue();
195             Node constructorNode = attr.getNamedItem(CONSTRUCTOR);
196             final String constructor = constructorNode == null ? null : constructorNode.getNodeValue();
197             //System.out.println("AdapterRegistry2.handleType: " + b + " " + uri + " " + interface_);
198             addInstaller(
199                     new AdapterInstaller() {
200
201                         @Override
202                         public void install(ReadGraph g, AdaptionService service) throws Exception {
203                                 try {
204                             Class<? extends T> clazz =
205                                 ((Class<?>)b.loadClass(attr.getNamedItem(CLASS).getNodeValue()))
206                                 .asSubclass(interface_);
207                             List<IDynamicAdapter2> parameters = readParameters(g, node, b);
208                             IDynamicAdapter2[] parameterArray = 
209                                 parameters.toArray(new IDynamicAdapter2[parameters.size()]);
210                             service.addAdapter(
211                                     g.getResource(uri),
212                                     interface_,
213                                     constructor == null 
214                                     ? new ReflectionAdapter2<T>(clazz, parameterArray)
215                                     : new StaticMethodAdapter<T>(clazz, constructor, parameterArray));
216                                 } catch(Error t) {
217                                         System.err.println("Failed to adapt "+interface_.getName());
218                                         throw t;
219                                 } catch(RuntimeException t) {
220                                         System.err.println("Failed to adapt "+interface_.getName());
221                                         throw t;
222                                 }
223                         }
224
225                     }, fileName);
226         } catch (Exception e) {
227             handleException(e, fileName);
228         }
229     }
230
231     private List<IDynamicAdapter2> readParameters(ReadGraph g, Node node, Loader b) throws DatabaseException, DOMException, ClassNotFoundException {
232         NodeList nodeList = node.getChildNodes();
233         ArrayList<IDynamicAdapter2> parameters = new ArrayList<IDynamicAdapter2>();
234         for(int i=0;i<nodeList.getLength();++i) {
235             Node n = nodeList.item(i);
236             if(n.getNodeType() == Node.ELEMENT_NODE) {
237                 NamedNodeMap attr = n.getAttributes();
238                 IDynamicAdapter2 da = null;
239                 if(n.getNodeName().equals("this"))
240                     da = new ThisResource2();
241                 else if(n.getNodeName().equals("graph"))
242                     da = new GraphObject2();
243                 else if(n.getNodeName().equals("bundle")) {
244                     String bundleId = null;
245                     Node fc = n.getFirstChild();
246                     if (fc != null)
247                         bundleId = fc.getNodeValue();
248                     if (bundleId == null) {
249                         da = new ConstantAdapter(Bundle.class, b.getBundle());
250                     } else {
251                         Bundle ob = Platform.getBundle(bundleId);
252                         if (ob != null) {
253                             da = new ConstantAdapter(Bundle.class, ob);
254                         } else {
255                             throw new DOMException(DOMException.NOT_FOUND_ERR, "bundle '" + bundleId + "' not found");
256                         }
257                     }
258                 } else if(n.getNodeName().equals("related"))
259                     da = new RelatedResources2(
260                             g.getResource(attr.getNamedItem("uri").getNodeValue()));
261                 else if(n.getNodeName().equals("orderedSet"))
262                     da = new OrderedSetResources2(
263                             g.getResource(attr.getNamedItem("uri").getNodeValue()));
264                 else if(n.getNodeName().equals("single"))
265                     da = new SingleRelatedResource2(
266                             g.getResource(attr.getNamedItem("uri").getNodeValue()));
267                 else if(n.getNodeName().equals("atMostOne"))
268                     da = new AtMostOneRelatedResource2(
269                             g.getResource(attr.getNamedItem("uri").getNodeValue()));
270                 else if(n.getNodeName().equals("string"))
271                     da = new ConstantAdapter(String.class, n.getFirstChild().getNodeValue());
272                 {
273                     Node toNode = attr.getNamedItem("to");
274                     if(toNode != null) {
275                         String to = toNode.getNodeValue();
276                         da = new AdaptingDynamicAdapter2(da, b.loadClass(to));
277                     }
278                 }
279                 parameters.add(da);
280             }
281         }
282         return parameters;
283     }
284
285     private <T> void handleAdapter(final Loader b, final Class<T> interface_, Node node, String fileName) {
286         try {
287             NamedNodeMap attr = node.getAttributes();
288             final String uri = attr.getNamedItem(URI).getNodeValue();
289             final String clazz = attr.getNamedItem(ADAPTER_CLASS).getNodeValue();
290
291             Node contextNode = attr.getNamedItem(CONTEXT_CLASS);
292             final Class<?> contextClass = contextNode != null ? b.loadClass(contextNode.getNodeValue()) : Resource.class;
293             
294             //System.out.println("AdapterRegistry2.handleAdapter: " + b + " " + uri + " " + interface_ + ", class=" + clazz);
295             addInstaller(
296                     new AdapterInstaller() {
297
298                         @SuppressWarnings("unchecked")
299                         @Override
300                         public void install(ReadGraph g, AdaptionService service) throws Exception {
301                             service.addAdapter(
302                                     g.getResource(uri),
303                                     interface_,
304                                     contextClass,
305                                     ((Class<?>)b.loadClass(clazz))
306                                     .asSubclass(Adapter.class).newInstance());
307                         }
308
309                     }, fileName);
310         } catch (Exception e) {
311             handleException(e, fileName);
312         }
313     }
314
315     private <T> void handleBaseType(Loader b, final Class<T> interface_, Node node, String fileName) {
316         try {
317             NamedNodeMap attr = node.getAttributes();
318             final String uri = attr.getNamedItem(URI).getNodeValue();
319             addInstaller(
320                     new AdapterInstaller() {
321
322                         @Override
323                         public void install(ReadGraph g, AdaptionService service) throws Exception {
324                             service.declareAdapter(
325                                     g.getResource(uri),
326                                     interface_);
327                         }
328
329                     }, fileName);
330         } catch (Exception e) {
331             handleException(e, fileName);
332         }
333     }
334
335     public void updateAdaptionService(Session s, final AdaptionService service) throws DatabaseException {
336         s.syncRequest(new Read() {
337             @Override
338             public Object perform(ReadGraph g) {
339                 for(AdapterInstaller t : installerSources.keySet()) {
340                     try {
341                         t.install(g, service);
342                     } catch (Exception e) {
343                         AdapterRegistry2.this.handleException(e, t);
344                     }
345                 }
346                 return null;
347             }
348         });
349     }
350
351     public void initialize(ClassLoader b, String schemaURL, File[] files) {
352
353         try {
354                 
355             DocumentBuilderFactory factory =
356                 DocumentBuilderFactory.newInstance();
357             
358             if(schemaURL != null && validateAgainstSchema()) {
359             
360                     factory.setValidating(true);
361                     factory.setAttribute(
362                             "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
363                     "http://www.w3.org/2001/XMLSchema");
364                     factory.setAttribute(
365                             "http://java.sun.com/xml/jaxp/properties/schemaSource", schemaURL);
366                     
367             }
368
369             // TODO Listen bundles (install/uninstall)
370             if (exceptions.isEmpty())
371                 for (final File f : files) {
372 //                        String fileName = new Path(b.getLocation()).append(file.getPath()).toString();
373                         try {
374                             DocumentBuilder builder = factory.newDocumentBuilder();
375                             builder.setErrorHandler(new ErrorHandler() {
376
377                                 @Override
378                                 public void error(SAXParseException exception)
379                                 throws SAXException {
380                                     // TODO Put this error somewhere
381                                     System.err.println("Parse error at " + f.getAbsolutePath() + 
382 //                                            + b.getSymbolicName() + "/adapters.xml" +
383                                             " line " + exception.getLineNumber() +
384                                             " column " + exception.getColumnNumber() + ":");
385                                     System.err.println(exception.getMessage());
386                                 }
387
388                                 @Override
389                                 public void fatalError(SAXParseException exception)
390                                 throws SAXException {
391                                     error(exception);
392                                 }
393
394                                 @Override
395                                 public void warning(SAXParseException exception)
396                                 throws SAXException {
397                                     error(exception);
398                                 }
399
400                             });
401                             //System.out.println("bundle=" + b.getSymbolicName());
402                             Document doc = builder.parse(f);
403                             handleAdaptersDocument(loader(b), doc, f.getAbsolutePath());
404                         } catch (Exception e) {
405                             handleException(e, f.getAbsolutePath());
406
407                         }
408                     }
409         } catch (Exception e) {
410             handleException(e, "(no file name available)");
411         }
412         
413     }
414
415     private boolean validateAgainstSchema() {
416         return Platform.inDevelopmentMode();
417     }
418
419         public void initialize(BundleContext context) {
420             LOGGER.info("Initializing");
421         try {
422                 
423             DocumentBuilderFactory factory =
424                 DocumentBuilderFactory.newInstance();
425
426             if (validateAgainstSchema()) {
427                 factory.setValidating(true);
428                 factory.setAttribute(
429                         "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
430                         "http://www.w3.org/2001/XMLSchema");
431                 factory.setAttribute(
432                         "http://java.sun.com/xml/jaxp/properties/schemaSource",
433                         context.getBundle().getResource("adapters.xsd").toString());
434             }
435
436             // TODO Listen bundles (install/uninstall)
437             List<Future<?>> waitFor = new ArrayList<>();
438             if (exceptions.isEmpty())
439                 for (final Bundle b : context.getBundles()) {
440                     Future<?> submit = ThreadUtils.getNonBlockingWorkExecutor().submit(() -> {
441                         URL file = b.getEntry(ADAPTERS_FILE);
442                         if (file != null) {
443                             String fileName = new Path(b.getLocation()).append(file.getPath()).toString();
444                             try {
445                                 DocumentBuilder builder = factory.newDocumentBuilder();
446                                 builder.setErrorHandler(new ErrorHandler() {
447
448                                     @Override
449                                     public void error(SAXParseException exception) throws SAXException {
450                                         // TODO Put this error somewhere
451                                         System.err.println("Parse error at " + b.getSymbolicName() + "/adapters.xml"
452                                                 + " line " + exception.getLineNumber() + " column "
453                                                 + exception.getColumnNumber() + ":");
454                                         System.err.println(exception.getMessage());
455                                     }
456
457                                     @Override
458                                     public void fatalError(SAXParseException exception) throws SAXException {
459                                         error(exception);
460                                     }
461
462                                     @Override
463                                     public void warning(SAXParseException exception) throws SAXException {
464                                         error(exception);
465                                     }
466
467                                 });
468
469                                 // System.out.println("bundle=" + b.getSymbolicName());
470                                 String text = FileUtils.getContents(file);
471                                 text = OntologyVersions.getInstance().currentVersion(text);
472                                 StringReader reader = new StringReader(text);
473                                 InputSource inputSource = new InputSource(reader);
474                                 Document doc = builder.parse(inputSource);
475                                 reader.close();
476                                 handleAdaptersDocument(loader(b), doc, fileName);
477                             } catch (Exception e) {
478                                 handleException(e, fileName);
479
480                             }
481                         }
482                     });
483                     waitFor.add(submit);
484                 }
485             // Let's wait in here
486             waitFor.forEach(f -> {
487                 try {
488                     f.get();
489                 } catch (InterruptedException | ExecutionException e) {
490                     LOGGER.error("Could not wait adapters to load", e);
491                 }
492             });
493             LOGGER.info("Adapters installed");
494         } catch (Exception e) {
495             handleException(e, "(no file name available)");
496         }
497     }
498
499         interface Loader {
500                 Class<?> loadClass(String name) throws ClassNotFoundException ;
501                 Bundle getBundle();
502         }
503
504         private Loader loader(final Bundle b) {
505                 return new Loader() {
506
507                         @Override
508                         public Class<?> loadClass(String name) throws ClassNotFoundException {
509                                 return b.loadClass(name);
510                         }
511                         
512                         @Override
513                         public Bundle getBundle() {
514                                 return b;
515                         }
516                         
517                 };
518         }
519
520         private Loader loader(final ClassLoader b) {
521                 return new Loader() {
522
523                         @Override
524                         public Class<?> loadClass(String name) throws ClassNotFoundException {
525                                 return b.loadClass(name);
526                         }
527
528                         @Override
529                         public Bundle getBundle() {
530                                 return null;
531                         }
532                         
533                 };
534         }
535         
536 }