/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.project.management; import java.io.BufferedInputStream; import java.io.Closeable; import java.io.DataInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Collectors; import org.eclipse.core.internal.runtime.PlatformActivator; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Platform; import org.eclipse.equinox.p2.metadata.IVersionedId; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionedId; import org.osgi.framework.Bundle; import org.simantics.databoard.Bindings; import org.simantics.databoard.adapter.AdaptException; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.container.DataContainer; import org.simantics.databoard.container.DataContainers; import org.simantics.graph.compiler.CompilationResult; import org.simantics.graph.compiler.GraphCompiler; import org.simantics.graph.compiler.GraphCompilerPreferences; import org.simantics.graph.compiler.ValidationMode; import org.simantics.graph.compiler.internal.ltk.FileSource; import org.simantics.graph.compiler.internal.ltk.ISource; import org.simantics.graph.compiler.internal.ltk.Problem; import org.simantics.graph.representation.Extensions; import org.simantics.graph.representation.TransferableGraph1; import org.simantics.graph.representation.TransferableGraphFileReader; import org.simantics.scl.reflection.OntologyVersions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class contains utilities for managing bundles in a active platform. * */ @SuppressWarnings("restriction") public class PlatformUtil { private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class); /** * Get all bundles in the platform. * * @return */ public static Bundle[] getBundles() { return PlatformActivator.getContext().getBundles(); } /** * Get the manifest file of a bundle * * @param bundle bundle * @return manifest or null if doesn't not exist * @throws IOException */ public static Manifest getManifest(Bundle bundle) throws IOException { URL url = bundle.getEntry("META-INF/MANIFEST.MF"); if (url==null) return null; try (InputStream is = url.openStream()) { return new Manifest(is); } } /** * Get the manifest file of a bundle * * @param bundle bundle * @return manifest or null if doesn't not exist * @throws IOException */ public static Manifest getSimanticsManifest(Bundle bundle) throws IOException { URL url = bundle.getEntry("META-INF/SIMANTICS.MF"); if (url==null) return null; try (InputStream is = url.openStream()) { return new Manifest(is); } } /** * Get a list (BundleIds) of all user installable units. These are the * top-level items that are visible for the end-user. * The list is acquired from the bundles of the current application. * * @param list of simantics features URIs * @throws IOException */ public static void getUserInstallableUnits(Collection list) throws IOException { for (Bundle bundle : getBundles()) { Manifest manifest = getSimanticsManifest(bundle); if (manifest==null) continue; Attributes attributes = manifest.getMainAttributes(); for (Entry entry2 : attributes.entrySet()) { Object key = entry2.getKey(); if (key.toString().contains("Installable-Unit")) { String bid = entry2.getValue().toString(); list.add( bid ); } } } } /** * Get all transferable graphs in the platform * * @param result collection to be filled with transferable graph info */ public static void getPlatformTGInfos(Collection result) { for (Bundle bundle : getBundles()) { Enumeration e = bundle.findEntries("graphs/", "*.tg", false); if (e==null) continue; while (e.hasMoreElements()) { org.osgi.framework.Version osgiVersion = bundle.getVersion(); Version p2Version = Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier()); String id = bundle.getSymbolicName(); TGInfo info = new TGInfo(); info.location = e.nextElement(); info.bundle = bundle; info.vid = new VersionedId(id, p2Version); result.add( info ); } } } private static void uncheckedClose(Closeable closeable) { try { if (closeable != null) closeable.close(); } catch (IOException e) { //ignore } } private static File copyResource(URL url, File targetFile) throws IOException, FileNotFoundException { FileOutputStream os = null; InputStream is = null; try { if (targetFile.exists()) targetFile.delete(); is = url.openStream(); int read; byte [] buffer = new byte [16384]; os = new FileOutputStream (targetFile); while ((read = is.read (buffer)) != -1) { os.write(buffer, 0, read); } os.close (); is.close (); return targetFile; } finally { uncheckedClose(os); uncheckedClose(is); } } private static File extractLib(URL libURL, String libName) throws FileNotFoundException, IOException { String tmpDirStr = System.getProperty("java.io.tmpdir"); if (tmpDirStr == null) throw new NullPointerException("java.io.tmpdir property is null"); File tmpDir = new File(tmpDirStr); File libFile = new File(tmpDir, libName); return copyResource(libURL, libFile); } private static File url2file(URL url, String fileName) { if ("file".equals(url.getProtocol())) { try { File path = new File(URLDecoder.decode(url.getPath(), "UTF-8")); return path; } catch (UnsupportedEncodingException e) { LOGGER.error("Failed to decode " + url, e); } } else if ("jar".equals(url.getProtocol())) { try { File libFile = extractLib(url, fileName); return libFile; } catch (FileNotFoundException e) { LOGGER.error("Extraction to " + fileName + " failed, url not found: " + url, e); } catch (IOException e) { LOGGER.error("Extraction to " + fileName + " failed from url " + url, e); } } else { LOGGER.error("Unsupported URL protocol '" + url + "' for reading as file '" + fileName + "'"); } return null; } public static Collection getTGFiles(Bundle b) { Enumeration enu = b.findEntries("/", "*.tg", false); if (enu == null) return Collections.emptyList(); if (!enu.hasMoreElements()) return Collections.emptyList(); ArrayList result = new ArrayList<>(); while (enu.hasMoreElements()) result.add(enu.nextElement()); return result; } public static void compile(Bundle b) throws Exception { Collection sources = new ArrayList<>(); Collection dependencies = new ArrayList<>(); for (Bundle b2 : getBundles()) { if(b.equals(b2)) continue; for (URL url : getTGFiles(b2)) { File graphFile = url2file(url, b2.toString() + url.toString()); dependencies.add(GraphCompiler.read(graphFile)); } } File bundleFile = FileLocator.getBundleFile(b); if(bundleFile.isDirectory()) { File folder = new File(bundleFile, "dynamicGraph"); for(File f : folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".pgraph"); } })) { sources.add(new FileSource(f)); } } // System.out.println("source is " + tmpFile.getAbsolutePath()); final StringBuilder errorStringBuilder = new StringBuilder(); GraphCompilerPreferences prefs = new GraphCompilerPreferences(); prefs.validate = true; prefs.validateRelationRestrictions = ValidationMode.ERROR; prefs.validateResourceHasType = ValidationMode.ERROR; String currentLayer0Version = OntologyVersions.getInstance().currentOntologyVersion("http://www.simantics.org/Layer0-0.0"); CompilationResult result = GraphCompiler.compile(currentLayer0Version, sources, dependencies, null, prefs); for(Problem problem : result.getErrors()) errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n"); for(Problem problem : result.getWarnings()) errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n"); if(errorStringBuilder.length() > 0) { LOGGER.error(errorStringBuilder.toString()); } else { GraphCompiler.write(new File(bundleFile, "graph.tg"), result.getGraph()); } } /** * Compile all dynamic ontologies in the Platform * * @param collection * @throws IOException */ public static void compileAllDynamicOntologies() { for (Bundle bundle : getBundles()) { if(bundle.getEntry("dynamicGraph") != null) { try { File bundleFile = FileLocator.getBundleFile(bundle); if(bundleFile.isDirectory()) { File tg = new File(bundleFile, "graph.tg"); long tgLastModified = tg.lastModified(); File folder = new File(bundleFile, "dynamicGraph"); for(File f : folder.listFiles()) { if(f.isFile() && f.getName().endsWith(".pgraph") && f.lastModified() > tgLastModified) { compile(bundle); break; } } } } catch (Throwable e) { LOGGER.error("Failed to compile dynamic ontologies in bundle " + bundle.getSymbolicName(), e); } } } } /** * Get all graphs in the Platform * * @param collection * @throws IOException */ public static Collection getAllGraphs() throws IOException { AtomicReference problem = new AtomicReference<>(); Collection gbundles = Arrays.stream(getBundles()) .parallel() .map(b -> { try { return problem.get() == null ? getGraphs(b) : Collections.emptyList(); } catch (IOException e) { if (LOGGER.isWarnEnabled()) LOGGER.debug("Could not get graph from bundle {}", b, e); problem.set(e); return Collections.emptyList(); } }) .flatMap(Collection::stream) .collect(Collectors.toList()); if (problem.get() != null) throw problem.get(); return gbundles; } /** * Get bundle * * @param symbolicName * @return bundle or null if there is no bundle or graph * @throws IOException */ public static GraphBundle getGraph(String symbolicName) throws IOException { Bundle bundle = Platform.getBundle(symbolicName); if (bundle == null) return null; return getGraph( bundle ); } public static Collection getGraphs(Bundle bundle) throws IOException { return getTGFiles(bundle).stream() .map(url -> { try { GraphBundleEx result = tryGetOnDemandGraph(bundle, url); return result != null ? result : getCompleteGraph(bundle, url); } catch (IOException e) { if (LOGGER.isWarnEnabled()) LOGGER.warn("Could not get graph from bundle url {}", url, e); return null; } }) .filter(Objects::nonNull) .collect(Collectors.toList()); } private static String tgFileId(Bundle bundle, URL url) { String urlString = url.toString(); String file = urlString.substring(urlString.lastIndexOf("/") + 1); return bundle.getSymbolicName() + "_" + file; } /** * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root. * * @param bundle * @return transferable graph, or null if there is no graph in the bundle. * @throws IOException */ public static GraphBundleEx getGraph(Bundle bundle) throws IOException { URL url = bundle.getEntry("graph.tg"); if (url == null) return null; GraphBundleEx result = tryGetOnDemandGraph(bundle, url); return result != null ? result : getCompleteGraph(bundle, url); } private static GraphBundleEx getCompleteGraph(Bundle bundle, URL url) throws IOException { try { String id = tgFileId(bundle, url); return new GraphBundleEx( getBundleName(bundle, id), readTG(url), new VersionedId(id, toP2Version(bundle)), isImmutable(bundle)); } catch (Exception e) { throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e); } catch (Error e) { LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e); throw e; } } /** * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root. * * @param bundle * @return transferable graph, or null if there is no graph in the bundle. * @throws IOException */ private static GraphBundleEx tryGetOnDemandGraph(Bundle bundle, URL url) throws IOException { try { Integer cachedHash = readCachedHash(url); if (cachedHash == null) { LOGGER.info("No cached hash for " + bundle); return null; } Supplier graphSource = () -> { try { return readTG(url); } catch (Exception e) { throw new RuntimeException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e); } catch (Error e) { LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e); throw e; } }; String id = tgFileId(bundle, url); return new GraphBundleEx( getBundleName(bundle, id), graphSource, cachedHash, new VersionedId(id, toP2Version(bundle)), isImmutable(bundle)); } catch (Exception e) { throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e); } } private static TransferableGraph1 readTG(URL url) throws Exception { try (InputStream is = url.openStream()) { return TransferableGraphFileReader.read(is); } } private static DataContainer readHeader(URL url) throws IOException { try (InputStream is = url.openStream()) { return DataContainers.readHeader(new DataInputStream(new BufferedInputStream(is, 1 << 14))); } } private static Integer readCachedHash(URL url) throws IOException, AdaptException { DataContainer header = readHeader(url); Variant hashCode = header.metadata.get(Extensions.CACHED_HASHCODE); return hashCode != null ? (Integer) hashCode.getValue(Bindings.INTEGER) : null; } private static Version toP2Version(Bundle bundle) { org.osgi.framework.Version osgiVersion = bundle.getVersion(); return Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier()); } private static String getBundleName(Bundle bundle, String id) { String name = (String) bundle.getHeaders().get("Bundle-Name"); return name != null ? name : id; } private static boolean isImmutable(Bundle bundle) { String immutable = (String) bundle.getHeaders().get("Immutable"); if(immutable == null) return true; if("false".equals(immutable)) return false; if("trueWhenDeployed".equals(immutable)) { String installHint = System.getProperty("installOntologiesAsDeployed"); if("true".equals(installHint)) return true; else return false; } return true; } public static class TGInfo { public Bundle bundle; public URL location; public IVersionedId vid; } }