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