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