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.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;
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;
67 * This class contains utilities for managing bundles in a active platform.
70 @SuppressWarnings("restriction")
71 public class PlatformUtil {
73 private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class);
76 * Get all bundles in the platform.
80 public static Bundle[] getBundles() {
81 return PlatformActivator.getContext().getBundles();
85 * Get the manifest file of a 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");
96 try (InputStream is = url.openStream()) {
97 return new Manifest(is);
102 * Get the manifest file of a bundle
106 * @return manifest or <tt>null</tt> if doesn't not exist
107 * @throws IOException
109 public static Manifest getSimanticsManifest(Bundle bundle) throws IOException {
110 URL url = bundle.getEntry("META-INF/SIMANTICS.MF");
113 try (InputStream is = url.openStream()) {
114 return new Manifest(is);
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.
124 * of simantics features URIs
125 * @throws IOException
127 public static void getUserInstallableUnits(Collection<String> list) throws IOException {
128 for (Bundle bundle : getBundles()) {
129 Manifest manifest = getSimanticsManifest(bundle);
130 if (manifest == null)
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();
144 * Get all transferable graphs in the platform
147 * collection to be filled with transferable graph info
149 public static void getPlatformTGInfos(Collection<TGInfo> result) {
150 for (Bundle bundle : getBundles()) {
151 Enumeration<URL> e = bundle.findEntries("graphs/", "*.tg", false);
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();
160 TGInfo info = new TGInfo();
161 info.location = e.nextElement();
162 info.bundle = bundle;
163 info.vid = new VersionedId(id, p2Version);
169 private static void uncheckedClose(Closeable closeable) {
171 if (closeable != null)
173 } catch (IOException e) {
178 private static File copyResource(URL url, File targetFile) throws IOException, FileNotFoundException {
179 FileOutputStream os = null;
180 InputStream is = null;
182 if (targetFile.exists())
185 is = url.openStream();
187 byte[] buffer = new byte[16384];
188 os = new FileOutputStream(targetFile);
189 while ((read = is.read(buffer)) != -1) {
190 os.write(buffer, 0, read);
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);
211 private static File url2file(URL url, String fileName) {
212 if ("file".equals(url.getProtocol())) {
214 File path = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
216 } catch (UnsupportedEncodingException e) {
217 LOGGER.error("Failed to decode " + url, e);
219 } else if ("jar".equals(url.getProtocol())) {
221 File libFile = extractLib(url, fileName);
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);
229 LOGGER.error("Unsupported URL protocol '" + url + "' for FastLZ native library file '" + fileName);
234 public static Collection<URL> getTGFiles(Bundle b) {
235 Enumeration<URL> enu = b.findEntries("/", "*.tg", false);
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());
247 public static void compile(Bundle b) throws Exception {
249 Collection<ISource> sources = new ArrayList<>();
250 Collection<TransferableGraph1> dependencies = new ArrayList<>();
252 for (Bundle b2 : getBundles()) {
255 for (URL url : getTGFiles(b2)) {
256 File graphFile = url2file(url, b2.toString() + url.toString());
257 dependencies.add(GraphCompiler.read(graphFile));
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() {
267 public boolean accept(File dir, String name) {
268 return name.endsWith(".pgraph");
272 sources.add(new FileSource(f));
276 // System.out.println("source is " + tmpFile.getAbsolutePath());
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);
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");
292 if (errorStringBuilder.length() > 0) {
293 LOGGER.error(errorStringBuilder.toString());
295 GraphCompiler.write(new File(bundleFile, "graph.tg"), result.getGraph());
301 * Compile all dynamic ontologies in the Platform
304 * @throws IOException
306 public static void compileAllDynamicOntologies() {
307 for (Bundle bundle : getBundles()) {
308 if (bundle.getEntry("dynamicGraph") != null) {
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) {
322 } catch (Throwable e) {
323 LOGGER.error("Failed to compile dynamic ontologies in bundle " + bundle.getSymbolicName(), e);
330 * Get all graphs in the Platform
333 * @throws IOException
335 public static Collection<GraphBundle> getAllGraphs() throws IOException {
336 AtomicReference<IOException> problem = new AtomicReference<>();
338 // Collection<GraphBundle> gbundles = Arrays.stream(getBundles())
339 // // #7806: Due to databoard Binding/Serializer construction process
341 // // not even the DataContainer.readHeader invocations can run in parallel,
343 // // due to recurring serializer construction for Variant datatypes.
344 // // Therefore, we must disable parallel loading for now.
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);
356 // .filter(Objects::nonNull)
357 // .collect(Collectors.toList());
359 Collection<GraphBundle> gbundles = Arrays.stream(getBundles())
360 // #7806: Due to databoard Binding/Serializer construction process
362 // not even the DataContainer.readHeader invocations can run in parallel, most
364 // due to recurring serializer construction for Variant datatypes.
365 // Therefore, we must disable parallel loading for now.
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);
376 }).flatMap(Collection::stream).filter(Objects::nonNull).collect(Collectors.toList());
378 if (problem.get() != null)
386 * @param symbolicName
387 * @return bundle or <tt>null</tt> if there is no bundle or graph
388 * @throws IOException
390 public static GraphBundle getGraph(String symbolicName) throws IOException {
391 Bundle bundle = Platform.getBundle(symbolicName);
394 return getGraph(bundle);
398 * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the
402 * @return transferable graph, or <tt>null</tt> if there is no graph in the
404 * @throws IOException
406 public static GraphBundleEx getGraph(Bundle bundle) throws IOException {
407 URL url = bundle.getEntry("graph.tg");
410 GraphBundleEx result = tryGetOnDemandGraph(bundle, url);
411 return result != null ? result : getCompleteGraph(bundle, url);
414 public static Collection<GraphBundleEx> getGraphs(Bundle bundle) throws IOException {
415 return getTGFiles(bundle).stream().map(url -> {
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);
424 }).filter(Objects::nonNull).collect(Collectors.toList());
427 private static GraphBundleEx getCompleteGraph(Bundle bundle, URL url) throws IOException {
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);
436 LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
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;
448 * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the
452 * @return transferable graph, or <tt>null</tt> if there is no graph in the
454 * @throws IOException
456 private static GraphBundleEx tryGetOnDemandGraph(Bundle bundle, URL url) throws IOException {
458 Integer cachedHash = readCachedHash(url);
459 if (cachedHash == null) {
460 LOGGER.info("No cached hash for " + bundle);
464 Supplier<TransferableGraph1> graphSource = () -> {
467 } catch (Exception e) {
468 throw new RuntimeException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
470 LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
475 String id = tgFileId(bundle, url);
477 System.err.println("tryGetOnDemandGraph: " + id);
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);
486 private static TransferableGraph1 readTG(URL url) throws Exception {
487 try (InputStream is = url.openStream()) {
488 return TransferableGraphFileReader.read(is);
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)));
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;
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());
510 private static String getBundleName(Bundle bundle, String id) {
511 String name = (String) bundle.getHeaders().get("Bundle-Name");
512 return name != null ? name : id;
515 private static boolean isImmutable(Bundle bundle) {
516 String immutable = (String) bundle.getHeaders().get("Immutable");
517 return immutable != null ? "true".equals(immutable) : true;
520 public static class TGInfo {
521 public Bundle bundle;
523 public IVersionedId vid;