1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.project.management;
14 import java.io.BufferedInputStream;
15 import java.io.Closeable;
16 import java.io.DataInputStream;
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;
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;
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;
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;
68 * This class contains utilities for managing bundles in a active platform.
71 @SuppressWarnings("restriction")
72 public class PlatformUtil {
74 private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class);
77 * Get all bundles in the platform.
81 public static Bundle[] getBundles() {
82 return PlatformActivator.getContext().getBundles();
86 * Get the manifest file of a bundle
88 * @param bundle bundle
89 * @return manifest or <tt>null</tt> if doesn't not exist
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);
101 * Get the manifest file of a bundle
103 * @param bundle bundle
104 * @return manifest or <tt>null</tt> if doesn't not exist
105 * @throws IOException
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);
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.
120 * @param list of simantics features URIs
121 * @throws IOException
123 public static void getUserInstallableUnits(Collection<String> list)
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();
141 * Get all transferable graphs in the platform
143 * @param result collection to be filled with transferable graph info
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();
154 TGInfo info = new TGInfo();
155 info.location = e.nextElement();
156 info.bundle = bundle;
157 info.vid = new VersionedId(id, p2Version);
163 private static void uncheckedClose(Closeable closeable) {
165 if (closeable != null)
167 } catch (IOException e) {
172 private static File copyResource(URL url, File targetFile) throws IOException, FileNotFoundException {
173 FileOutputStream os = null;
174 InputStream is = null;
176 if (targetFile.exists())
179 is = url.openStream();
181 byte [] buffer = new byte [16384];
182 os = new FileOutputStream (targetFile);
183 while ((read = is.read (buffer)) != -1) {
184 os.write(buffer, 0, read);
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);
205 private static File url2file(URL url, String fileName) {
206 if ("file".equals(url.getProtocol())) {
208 File path = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
210 } catch (UnsupportedEncodingException e) {
211 LOGGER.error("Failed to decode " + url, e);
213 } else if ("jar".equals(url.getProtocol())) {
215 File libFile = extractLib(url, fileName);
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);
223 LOGGER.error("Unsupported URL protocol '" + url + "' for FastLZ native library file '" + fileName);
228 public static void compile(Bundle b) throws Exception {
230 Collection<ISource> sources = new ArrayList<>();
231 Collection<TransferableGraph1> dependencies = new ArrayList<>();
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));
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() {
247 public boolean accept(File dir, String name) {
248 return name.endsWith(".pgraph");
252 sources.add(new FileSource(f));
256 // System.out.println("source is " + tmpFile.getAbsolutePath());
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);
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");
271 if(errorStringBuilder.length() > 0) {
272 LOGGER.error(errorStringBuilder.toString());
274 GraphCompiler.write(new File(bundleFile, "graph.tg"), result.getGraph());
280 * Compile all dynamic ontologies in the Platform
283 * @throws IOException
285 public static void compileAllDynamicOntologies() {
286 for (Bundle bundle : getBundles()) {
287 if(bundle.getEntry("dynamicGraph") != null) {
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) {
301 } catch (Throwable e) {
302 LOGGER.error("Failed to compile dynamic ontologies in bundle " + bundle.getSymbolicName(), e);
309 * Get all graphs in the Platform
312 * @throws IOException
314 public static Collection<GraphBundle> getAllGraphs() throws IOException {
315 AtomicReference<IOException> problem = new AtomicReference<>();
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.
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);
333 .filter(Objects::nonNull)
334 .collect(Collectors.toList());
336 if (problem.get() != null)
344 * @param symbolicName
345 * @return bundle or <tt>null</tt> if there is no bundle or graph
346 * @throws IOException
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 );
355 * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root.
358 * @return transferable graph, or <tt>null</tt> if there is no graph in the bundle.
359 * @throws IOException
361 public static GraphBundleEx getGraph(Bundle bundle) throws IOException {
362 URL url = bundle.getEntry("graph.tg");
365 GraphBundleEx result = tryGetOnDemandGraph(bundle, url);
366 return result != null ? result : getCompleteGraph(bundle, url);
369 private static GraphBundleEx getCompleteGraph(Bundle bundle, URL url) throws IOException {
371 String id = bundle.getSymbolicName();
372 return new GraphBundleEx(
373 getBundleName(bundle, id),
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);
380 LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
386 * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root.
389 * @return transferable graph, or <tt>null</tt> if there is no graph in the bundle.
390 * @throws IOException
392 private static GraphBundleEx tryGetOnDemandGraph(Bundle bundle, URL url) throws IOException {
394 Integer cachedHash = readCachedHash(url);
395 if (cachedHash == null)
398 Supplier<TransferableGraph1> graphSource = () -> {
401 } catch (Exception e) {
402 throw new RuntimeException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
404 LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
409 String id = bundle.getSymbolicName();
411 return new GraphBundleEx(
412 getBundleName(bundle, id),
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);
422 private static FormatHandler<TransferableGraph1> FORMAT_HANDLER = new FormatHandler<TransferableGraph1>() {
424 public Binding getBinding() {
425 return TransferableGraph1.BINDING;
428 public TransferableGraph1 process(DataContainer container) throws Exception {
429 return (TransferableGraph1) container.content.getValue(TransferableGraph1.BINDING);
433 @SuppressWarnings("unchecked")
434 private static Map<String, FormatHandler<TransferableGraph1>> handlers = ArrayMap.make(
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();
449 return DataContainers.readFile(new DataInputStream(is), handlers);
452 private static TransferableGraph1 readTG(URL url) throws Exception {
453 try (InputStream is = url.openStream()) {
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)));
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;
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());
475 private static String getBundleName(Bundle bundle, String id) {
476 String name = (String) bundle.getHeaders().get("Bundle-Name");
477 return name != null ? name : id;
480 private static boolean isImmutable(Bundle bundle) {
481 String immutable = (String) bundle.getHeaders().get("Immutable");
482 return immutable != null ? "true".equals(immutable) : true;
485 public static class TGInfo {
486 public Bundle bundle;
488 public IVersionedId vid;