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