]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.project/src/org/simantics/project/management/PlatformUtil.java
ebd987e3f2b5238e38ba4ace5ce1944a9eea6e55
[simantics/platform.git] / bundles / org.simantics.project / src / org / simantics / project / management / PlatformUtil.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.project.management;
13
14 import java.io.BufferedInputStream;
15 import java.io.Closeable;
16 import java.io.DataInput;
17 import java.io.DataInputStream;
18 import java.io.File;
19 import java.io.FileNotFoundException;
20 import java.io.FileOutputStream;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Enumeration;
31 import java.util.Map.Entry;
32 import java.util.Objects;
33 import java.util.concurrent.CompletableFuture;
34 import java.util.concurrent.ExecutionException;
35 import java.util.jar.Attributes;
36 import java.util.jar.Manifest;
37 import java.util.stream.Collectors;
38
39 import org.eclipse.core.internal.runtime.PlatformActivator;
40 import org.eclipse.core.runtime.FileLocator;
41 import org.eclipse.core.runtime.Platform;
42 import org.eclipse.equinox.p2.metadata.IVersionedId;
43 import org.eclipse.equinox.p2.metadata.Version;
44 import org.eclipse.equinox.p2.metadata.VersionedId;
45 import org.osgi.framework.Bundle;
46 import org.simantics.databoard.binding.Binding;
47 import org.simantics.databoard.binding.mutable.Variant;
48 import org.simantics.databoard.container.DataContainer;
49 import org.simantics.databoard.container.DataContainers;
50 import org.simantics.graph.compiler.CompilationResult;
51 import org.simantics.graph.compiler.GraphCompiler;
52 import org.simantics.graph.compiler.GraphCompilerPreferences;
53 import org.simantics.graph.compiler.ValidationMode;
54 import org.simantics.graph.representation.TransferableGraph1;
55 import org.simantics.ltk.FileSource;
56 import org.simantics.ltk.ISource;
57 import org.simantics.ltk.Problem;
58 import org.simantics.scl.reflection.OntologyVersions;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 /**
63  * This class contains utilities for managing bundles in a active platform. 
64  *
65  */
66 @SuppressWarnings("restriction")
67 public class PlatformUtil {
68
69         private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class);
70
71         /**
72          * Get all bundles in the platform.
73          * 
74          * @return
75          */
76         public static Bundle[] getBundles() {
77                 return PlatformActivator.getContext().getBundles();
78         }
79                 
80         /**
81          * Get the manifest file of a bundle
82          * 
83          * @param bundle bundle
84          * @return manifest or <tt>null</tt> if doesn't not exist
85          * @throws IOException 
86          */
87         public static Manifest getManifest(Bundle bundle) throws IOException {
88                 URL url = bundle.getEntry("META-INF/MANIFEST.MF");
89                 if (url==null) return null;
90                 InputStream is = url.openStream();
91                 try {
92                         return new Manifest(is);                        
93                 } finally {
94                         is.close();
95                 }
96         }
97         
98         /**
99          * Get the manifest file of a bundle
100          * 
101          * @param bundle bundle
102          * @return manifest or <tt>null</tt> if doesn't not exist
103          * @throws IOException 
104          */
105         public static Manifest getSimanticsManifest(Bundle bundle) throws IOException {
106                 URL url = bundle.getEntry("META-INF/SIMANTICS.MF");
107                 if (url==null) return null;
108                 InputStream is = url.openStream();
109                 try {
110                         return new Manifest(is);                        
111                 } finally {
112                         is.close();
113                 }
114         }
115         
116         /**
117          * Get a list (BundleIds) of all user installable units. These are the 
118          * top-level items that are visible for the end-user. 
119          * The list is acquired from the bundles of the current application. 
120          * 
121          * @param list of simantics features URIs
122          * @throws IOException 
123          */
124         public static void getUserInstallableUnits(Collection<String> list) 
125         throws IOException 
126         {
127                 for (Bundle bundle : getBundles()) {
128                         Manifest manifest = getSimanticsManifest(bundle);
129                         if (manifest==null) continue;
130                         Attributes attributes = manifest.getMainAttributes();
131                         for (Entry<Object, Object> entry2 : attributes.entrySet()) {
132                                 Object key = entry2.getKey();
133                         if (key.toString().contains("Installable-Unit")) {
134                                 String bid = entry2.getValue().toString();                              
135                                 list.add( bid );
136                         }
137                         }
138                 }               
139         }
140         
141         /**
142          * Get all transferable graphs in the platform
143          * 
144          * @param result collection to be filled with transferable graph info 
145          */
146         public static void getPlatformTGInfos(Collection<TGInfo> result) {
147                 for (Bundle bundle : getBundles()) {
148                         Enumeration<URL> e = bundle.findEntries("graphs/", "*.tg", false);
149                         if (e==null) continue;
150                         while (e.hasMoreElements()) {
151                                 org.osgi.framework.Version osgiVersion = bundle.getVersion();
152                                 Version p2Version = Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier());
153                                 String id = bundle.getSymbolicName();
154                                 
155                                 TGInfo info = new TGInfo();
156                                 info.location = e.nextElement();
157                                 info.bundle = bundle;
158                                 info.vid = new VersionedId(id, p2Version);
159                                 result.add( info );
160                         }
161                 }
162         }
163
164     private static void uncheckedClose(Closeable closeable) {
165         try {
166             if (closeable != null)
167                 closeable.close();
168         } catch (IOException e) {
169             //ignore
170         }
171     }
172         
173     private static File copyResource(URL url, File targetFile) throws IOException, FileNotFoundException {
174         FileOutputStream os = null;
175         InputStream is = null;
176         try {
177             if (targetFile.exists())
178                 targetFile.delete();
179
180             is = url.openStream();
181             int read;
182             byte [] buffer = new byte [16384];
183             os = new FileOutputStream (targetFile);
184             while ((read = is.read (buffer)) != -1) {
185                 os.write(buffer, 0, read);
186             }
187             os.close ();
188             is.close ();
189
190             return targetFile;
191         } finally {
192             uncheckedClose(os);
193             uncheckedClose(is);
194         }
195     }
196         
197     private static File extractLib(URL libURL, String libName) throws FileNotFoundException, IOException {
198         String tmpDirStr = System.getProperty("java.io.tmpdir");
199         if (tmpDirStr == null)
200             throw new NullPointerException("java.io.tmpdir property is null");
201         File tmpDir = new File(tmpDirStr);
202         File libFile = new File(tmpDir, libName);
203         return copyResource(libURL, libFile);
204     }
205         
206     private static File url2file(URL url, String fileName) {
207                 if ("file".equals(url.getProtocol())) {
208                         try {
209                                 File path = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
210                                 return path;
211                         } catch (UnsupportedEncodingException e) {
212                                 LOGGER.error("Failed to decode " + url, e);
213                         }
214                 } else if ("jar".equals(url.getProtocol())) {
215                         try {
216                                 File libFile = extractLib(url, fileName);
217                                 return libFile;
218                         } catch (FileNotFoundException e) {
219                                 LOGGER.error("Extraction to " + fileName + " failed, url not found: " + url, e);
220                         } catch (IOException e) {
221                                 LOGGER.error("Extraction to " + fileName + " failed from url " + url, e);
222                         }
223                 } else {
224                         LOGGER.error("Unsupported URL protocol '" + url + "' for FastLZ native library file '" + fileName);
225                 }       
226                 return null;
227         }
228         
229         public static void compile(Bundle b) throws Exception {
230                 
231                 Collection<ISource> sources = new ArrayList<ISource>();
232                 Collection<TransferableGraph1> dependencies = new ArrayList<TransferableGraph1>();
233                 
234                 for (Bundle b2 : getBundles()) {
235                         if(b.equals(b2)) continue;
236                         URL url = b2.getEntry("graph.tg");
237                         if (url==null) continue;
238                         File graphFile = url2file(FileLocator.resolve(b2.getEntry("/graph.tg")), b2.toString());
239                         dependencies.add(GraphCompiler.read(graphFile));
240                 }
241                 
242                 File bundleFile = FileLocator.getBundleFile(b);
243                 if(bundleFile.isDirectory()) {
244                         File folder = new File(bundleFile, "dynamicGraph");
245                         for(File f : folder.listFiles(new FilenameFilter() {
246                                 
247                                 @Override
248                                 public boolean accept(File dir, String name) {
249                                         return name.endsWith(".pgraph");
250                                 }
251                                 
252                         })) {
253                                 sources.add(new FileSource(f));
254                         }
255                 }               
256                 
257 //              System.out.println("source is " + tmpFile.getAbsolutePath());
258                 
259                 final StringBuilder errorStringBuilder = new StringBuilder();
260                 GraphCompilerPreferences prefs = new GraphCompilerPreferences();
261                 prefs.validate = true;
262                 prefs.validateRelationRestrictions = ValidationMode.ERROR;
263                 prefs.validateResourceHasType = ValidationMode.ERROR;
264                 String currentLayer0Version = OntologyVersions.getInstance().currentOntologyVersion("http://www.simantics.org/Layer0-0.0");
265                 CompilationResult result = GraphCompiler.compile(currentLayer0Version, sources, dependencies, null, prefs);
266                 
267                 for(Problem problem : result.getErrors())
268                         errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n");
269                 for(Problem problem : result.getWarnings())
270                         errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n");
271
272                 if(errorStringBuilder.length() > 0) {
273                         LOGGER.error(errorStringBuilder.toString());
274                 } else {
275                         DataContainers.writeFile(new File(bundleFile, "graph.tg"), 
276                                         new DataContainer("graph", 1, new Variant(TransferableGraph1.BINDING, result.getGraph())));
277                 }
278                 
279         }
280         
281         /**
282          * Compile all dynamic ontologies in the Platform
283          * 
284          * @param collection
285          * @throws IOException
286          */
287         public static void compileAllDynamicOntologies() {
288                 for (Bundle bundle : getBundles()) {
289                         if(bundle.getEntry("dynamicGraph") != null) {
290                                 try {
291                                         File bundleFile = FileLocator.getBundleFile(bundle);
292                                         if(bundleFile.isDirectory()) {
293                                                 File tg = new File(bundleFile, "graph.tg");
294                                                 long tgLastModified = tg.lastModified();
295                                                 File folder = new File(bundleFile, "dynamicGraph");
296                                                 for(File f : folder.listFiles()) {
297                                                         if(f.isFile() && f.getName().endsWith(".pgraph") && f.lastModified() > tgLastModified) {
298                                                                 compile(bundle);
299                                                                 break;
300                                                         }
301                                                 }
302                                         }
303                                 } catch (Throwable e) {
304                                         LOGGER.error("Failed to compile dynamic ontologies in bundle " + bundle.getSymbolicName(), e);
305                                 }
306                         }
307                 }
308         }
309         
310         /**
311          * Get all graphs in the Platform
312          * 
313          * @param collection
314          * @throws IOException
315          */
316     public static Collection<GraphBundle> getAllGraphs() throws IOException {
317         CompletableFuture<Object> f = new CompletableFuture<>();
318         Bundle[] bundles = getBundles();
319         Collection<GraphBundle> gbundles = Arrays.stream(bundles).map(t -> { // this could be done in parallel in the future?
320             if (f.isCompletedExceptionally())
321                 return null;
322             try {
323                 return PlatformUtil.getGraph(t);
324             } catch (IOException e) {
325                 if (LOGGER.isDebugEnabled())
326                     LOGGER.debug("Could not get graph {}", t, e);
327                 f.completeExceptionally(e);
328                 return null;
329             }
330         }).filter(Objects::nonNull).collect(Collectors.toList());
331         if (f.isCompletedExceptionally()) {
332             try {
333                 f.get();
334             } catch (ExecutionException | InterruptedException e) {
335                 throw (IOException) e.getCause();
336             }
337         }
338         return gbundles;
339     }
340
341         /**
342          * Get bundle 
343          * 
344          * @param symbolicName
345          * @return bundle or <tt>null</tt> if there is no bundle or graph 
346          * @throws IOException
347          */
348         public static GraphBundle getGraph(String symbolicName) throws IOException {
349                 Bundle bundle = Platform.getBundle(symbolicName);
350                 if (bundle == null) return null;
351                 return getGraph( bundle );
352         }
353         
354         /**
355          * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root.
356          * 
357          * @param bundle
358          * @return transferable graph, or <tt>null</tt> if there is no graph in the bundle. 
359          * @throws IOException 
360          */
361         public static GraphBundleEx getGraph(Bundle bundle) throws IOException {
362                 URL url = bundle.getEntry("graph.tg");
363                 
364                 if (url==null) return null;
365                 InputStream is = url.openStream(); 
366                 // NOTE: this is vital for performance.
367                 is = new BufferedInputStream(is, 128*1024);
368                 try {
369                         DataInput dis = new DataInputStream(is);
370                         // or
371                         // dis = new InputStreamReadable(is, <max limit>) to protect from OOM
372                         
373                         org.simantics.databoard.container.DataContainer container = 
374                                         DataContainers.readFile(dis); 
375
376                         Binding binding = TransferableGraph1.BINDING;
377                         TransferableGraph1 graph = (TransferableGraph1)container.content.getValue(binding);
378 //                      TransferableGraph1 graph = (TransferableGraph1) Files.readFile(is, binding);
379 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): read transferable graph in " + (System.nanoTime()-start)*1e-6 + "ms");
380                         org.osgi.framework.Version osgiVersion = bundle.getVersion();
381                         Version p2Version = Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier());
382                         String id = bundle.getSymbolicName();
383                         VersionedId vid = new VersionedId(id, p2Version);
384                         String name = (String) bundle.getHeaders().get("Bundle-Name");
385                         if (name == null) name = id;
386                         String immutable = (String) bundle.getHeaders().get("Immutable");
387                         boolean isImmutable = 
388                                         immutable != null ? 
389                                                         "true".equals(immutable) : 
390                                                                 true;
391
392 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): before hashcode calculation in " + (System.nanoTime()-start)*1e-6 + "ms");
393                         GraphBundleEx entry = new GraphBundleEx(name, graph, vid, isImmutable);
394 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): completed in " + (System.nanoTime()-start)*1e-6 + "ms");
395                         return entry;
396                 } catch (Exception e) {
397                         throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
398                 } catch (Error e) {
399                         LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
400                         throw e;
401                 } finally {
402                         is.close();
403                 }
404         }
405
406         public static class TGInfo {
407                 public Bundle bundle;
408                 public URL location;
409                 public IVersionedId vid;
410         }
411
412 }