]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.project/src/org/simantics/project/management/PlatformUtil.java
9dfa67efef7be394081c583f325b09ed63dfea4e
[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                                 // #7806: Due to databoard Binding/Serializer construction process thread-unsafety
319                                 // not even the DataContainer.readHeader invocations can run in parallel, most likely
320                                 // due to recurring serializer construction for Variant datatypes.
321                                 // Therefore, we must disable parallel loading for now.
322                                 //.parallel()
323                                 .map(b -> {
324                                         try {
325                                                 return problem.get() == null ? getGraph(b) : null;
326                                         } catch (IOException e) {
327                                                 if (LOGGER.isDebugEnabled())
328                                                         LOGGER.debug("Could not get graph from bundle {}", b, e);
329                                                 problem.set(e);
330                                                 return null;
331                                         }
332                                 })
333                                 .filter(Objects::nonNull)
334                                 .collect(Collectors.toList());
335
336                 if (problem.get() != null)
337                         throw problem.get();
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                 if (url == null)
364                         return null;
365                 GraphBundleEx result = tryGetOnDemandGraph(bundle, url);
366                 return result != null ? result : getCompleteGraph(bundle, url);
367         }
368
369         private static GraphBundleEx getCompleteGraph(Bundle bundle, URL url) throws IOException {
370                 try {
371                         String id = bundle.getSymbolicName();
372                         return new GraphBundleEx(
373                                         getBundleName(bundle, id),
374                                         readTG(url),
375                                         new VersionedId(id, toP2Version(bundle)),
376                                         isImmutable(bundle));
377                 } catch (Exception e) {
378                         throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
379                 } catch (Error e) {
380                         LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
381                         throw e;
382                 }
383         }
384
385         /**
386          * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root.
387          * 
388          * @param bundle
389          * @return transferable graph, or <tt>null</tt> if there is no graph in the bundle. 
390          * @throws IOException 
391          */
392         private static GraphBundleEx tryGetOnDemandGraph(Bundle bundle, URL url) throws IOException {
393                 try {
394                         Integer cachedHash = readCachedHash(url);
395                         if (cachedHash == null)
396                                 return null;
397
398                         Supplier<TransferableGraph1> graphSource = () -> {
399                                 try {
400                                         return readTG(url);
401                                 } catch (Exception e) {
402                                         throw new RuntimeException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
403                                 } catch (Error e) {
404                                         LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
405                                         throw e;
406                                 }
407                         };
408
409                         String id = bundle.getSymbolicName();
410
411                         return new GraphBundleEx(
412                                         getBundleName(bundle, id),
413                                         graphSource,
414                                         cachedHash,
415                                         new VersionedId(id, toP2Version(bundle)),
416                                         isImmutable(bundle));
417                 } catch (Exception e) {
418                         throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
419                 }
420         }
421         
422         private static FormatHandler<TransferableGraph1> FORMAT_HANDLER = new FormatHandler<TransferableGraph1>() {
423         @Override
424         public Binding getBinding() {
425             return TransferableGraph1.BINDING;
426         }
427         @Override
428         public TransferableGraph1 process(DataContainer container) throws Exception {
429             return (TransferableGraph1) container.content.getValue(TransferableGraph1.BINDING);
430         }
431     };
432
433         @SuppressWarnings("unchecked")
434         private static Map<String, FormatHandler<TransferableGraph1>> handlers = ArrayMap.make(
435                         new String[] {
436                                         "graph:1",
437                                         "sharedLibrary:1"
438                         },
439                         FORMAT_HANDLER,
440                         FORMAT_HANDLER);
441
442         private static TransferableGraph1 readTG(InputStream is) throws Exception {
443                 // For an unknown reason this is totally broken when running the TestSCLOsgi
444                 // in the SDK Tycho build. It returns incomplete results because the
445                 // ReadableByteChannel used by ByteFileReader starts returning 0 unexpectedly.
446 //                              try (TransferableGraphFileReader reader = new TransferableGraphFileReader(is)) {
447 //                      return reader.readTG();
448 //              }
449                 return DataContainers.readFile(new DataInputStream(is), handlers);
450         }
451
452         private static TransferableGraph1 readTG(URL url) throws Exception {
453                 try (InputStream is = url.openStream()) {
454                         return readTG(is);
455                 }
456         }
457
458         private static DataContainer readHeader(URL url) throws IOException {
459                 try (InputStream is = url.openStream()) {
460                         return DataContainers.readHeader(new DataInputStream(new BufferedInputStream(is, 1 << 14)));
461                 }
462         }
463
464         private static Integer readCachedHash(URL url) throws IOException, AdaptException {
465                 DataContainer header = readHeader(url);
466                 Variant hashCode = header.metadata.get(Extensions.CACHED_HASHCODE);
467                 return hashCode != null ? (Integer) hashCode.getValue(Bindings.INTEGER) : null;
468         }
469
470         private static Version toP2Version(Bundle bundle) {
471                 org.osgi.framework.Version osgiVersion = bundle.getVersion();
472                 return Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier());
473         }
474
475         private static String getBundleName(Bundle bundle, String id) {
476                 String name = (String) bundle.getHeaders().get("Bundle-Name");
477                 return name != null ? name : id;
478         }
479
480         private static boolean isImmutable(Bundle bundle) {
481                 String immutable = (String) bundle.getHeaders().get("Immutable");
482                 return immutable != null ? "true".equals(immutable) : true;
483         }
484
485         public static class TGInfo {
486                 public Bundle bundle;
487                 public URL location;
488                 public IVersionedId vid;
489         }
490
491 }