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