Improved exception information and logging in PlatformUtil.getGraph()
[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.DataInput;
17 import java.io.DataInputStream;
18 import java.io.File;
19 import java.io.FileNotFoundException;
20 import java.io.FileOutputStream;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Enumeration;
30 import java.util.Map.Entry;
31 import java.util.jar.Attributes;
32 import java.util.jar.Manifest;
33
34 import org.eclipse.core.internal.runtime.PlatformActivator;
35 import org.eclipse.core.runtime.FileLocator;
36 import org.eclipse.core.runtime.Platform;
37 import org.eclipse.equinox.p2.metadata.IVersionedId;
38 import org.eclipse.equinox.p2.metadata.Version;
39 import org.eclipse.equinox.p2.metadata.VersionedId;
40 import org.osgi.framework.Bundle;
41 import org.simantics.databoard.adapter.AdaptException;
42 import org.simantics.databoard.binding.Binding;
43 import org.simantics.databoard.binding.mutable.Variant;
44 import org.simantics.databoard.container.DataContainer;
45 import org.simantics.databoard.container.DataContainers;
46 import org.simantics.databoard.serialization.SerializationException;
47 import org.simantics.graph.compiler.CompilationResult;
48 import org.simantics.graph.compiler.GraphCompiler;
49 import org.simantics.graph.compiler.GraphCompilerPreferences;
50 import org.simantics.graph.compiler.ValidationMode;
51 import org.simantics.graph.representation.TransferableGraph1;
52 import org.simantics.ltk.FileSource;
53 import org.simantics.ltk.ISource;
54 import org.simantics.ltk.Problem;
55 import org.simantics.scl.reflection.OntologyVersions;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * This class contains utilities for managing bundles in a active platform. 
61  *
62  */
63 @SuppressWarnings("restriction")
64 public class PlatformUtil {
65
66         private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class);
67
68         /**
69          * Get all bundles in the platform.
70          * 
71          * @return
72          */
73         public static Bundle[] getBundles() {
74                 return PlatformActivator.getContext().getBundles();
75         }
76                 
77         /**
78          * Get the manifest file of a bundle
79          * 
80          * @param bundle bundle
81          * @return manifest or <tt>null</tt> if doesn't not exist
82          * @throws IOException 
83          */
84         public static Manifest getManifest(Bundle bundle) throws IOException {
85                 URL url = bundle.getEntry("META-INF/MANIFEST.MF");
86                 if (url==null) return null;
87                 InputStream is = url.openStream();
88                 try {
89                         return new Manifest(is);                        
90                 } finally {
91                         is.close();
92                 }
93         }
94         
95         /**
96          * Get the manifest file of a bundle
97          * 
98          * @param bundle bundle
99          * @return manifest or <tt>null</tt> if doesn't not exist
100          * @throws IOException 
101          */
102         public static Manifest getSimanticsManifest(Bundle bundle) throws IOException {
103                 URL url = bundle.getEntry("META-INF/SIMANTICS.MF");
104                 if (url==null) return null;
105                 InputStream is = url.openStream();
106                 try {
107                         return new Manifest(is);                        
108                 } finally {
109                         is.close();
110                 }
111         }
112         
113         /**
114          * Get a list (BundleIds) of all user installable units. These are the 
115          * top-level items that are visible for the end-user. 
116          * The list is acquired from the bundles of the current application. 
117          * 
118          * @param list of simantics features URIs
119          * @throws IOException 
120          */
121         public static void getUserInstallableUnits(Collection<String> list) 
122         throws IOException 
123         {
124                 for (Bundle bundle : getBundles()) {
125                         Manifest manifest = getSimanticsManifest(bundle);
126                         if (manifest==null) continue;
127                         Attributes attributes = manifest.getMainAttributes();
128                         for (Entry<Object, Object> entry2 : attributes.entrySet()) {
129                                 Object key = entry2.getKey();
130                         if (key.toString().contains("Installable-Unit")) {
131                                 String bid = entry2.getValue().toString();                              
132                                 list.add( bid );
133                         }
134                         }
135                 }               
136         }
137         
138         /**
139          * Get all transferable graphs in the platform
140          * 
141          * @param result collection to be filled with transferable graph info 
142          */
143         public static void getPlatformTGInfos(Collection<TGInfo> result) {
144                 for (Bundle bundle : getBundles()) {
145                         Enumeration<URL> e = bundle.findEntries("graphs/", "*.tg", false);
146                         if (e==null) continue;
147                         while (e.hasMoreElements()) {
148                                 org.osgi.framework.Version osgiVersion = bundle.getVersion();
149                                 Version p2Version = Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier());
150                                 String id = bundle.getSymbolicName();
151                                 
152                                 TGInfo info = new TGInfo();
153                                 info.location = e.nextElement();
154                                 info.bundle = bundle;
155                                 info.vid = new VersionedId(id, p2Version);
156                                 result.add( info );
157                         }
158                 }
159         }
160
161     private static void uncheckedClose(Closeable closeable) {
162         try {
163             if (closeable != null)
164                 closeable.close();
165         } catch (IOException e) {
166             //ignore
167         }
168     }
169         
170     private static File copyResource(URL url, File targetFile) throws IOException, FileNotFoundException {
171         FileOutputStream os = null;
172         InputStream is = null;
173         try {
174             if (targetFile.exists())
175                 targetFile.delete();
176
177             is = url.openStream();
178             int read;
179             byte [] buffer = new byte [16384];
180             os = new FileOutputStream (targetFile);
181             while ((read = is.read (buffer)) != -1) {
182                 os.write(buffer, 0, read);
183             }
184             os.close ();
185             is.close ();
186
187             return targetFile;
188         } finally {
189             uncheckedClose(os);
190             uncheckedClose(is);
191         }
192     }
193         
194     private static File extractLib(URL libURL, String libName) throws FileNotFoundException, IOException {
195         String tmpDirStr = System.getProperty("java.io.tmpdir");
196         if (tmpDirStr == null)
197             throw new NullPointerException("java.io.tmpdir property is null");
198         File tmpDir = new File(tmpDirStr);
199         File libFile = new File(tmpDir, libName);
200         return copyResource(libURL, libFile);
201     }
202         
203     private static File url2file(URL url, String fileName) {
204                 if ("file".equals(url.getProtocol())) {
205                         try {
206                                 File path = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
207                                 return path;
208                         } catch (UnsupportedEncodingException e) {
209                                 LOGGER.error("Failed to decode " + url, e);
210                         }
211                 } else if ("jar".equals(url.getProtocol())) {
212                         try {
213                                 File libFile = extractLib(url, fileName);
214                                 return libFile;
215                         } catch (FileNotFoundException e) {
216                                 LOGGER.error("Extraction to " + fileName + " failed, url not found: " + url, e);
217                         } catch (IOException e) {
218                                 LOGGER.error("Extraction to " + fileName + " failed from url " + url, e);
219                         }
220                 } else {
221                         System.err.println("Unsupported URL protocol '" + url + "' for FastLZ native library file '" + fileName);
222                 }       
223                 return null;
224         }
225         
226         public static void compile(Bundle b) throws Exception {
227                 
228                 Collection<ISource> sources = new ArrayList<ISource>();
229                 Collection<TransferableGraph1> dependencies = new ArrayList<TransferableGraph1>();
230                 
231                 for (Bundle b2 : getBundles()) {
232                         if(b.equals(b2)) continue;
233                         URL url = b2.getEntry("graph.tg");
234                         if (url==null) continue;
235                         File graphFile = url2file(FileLocator.resolve(b2.getEntry("/graph.tg")), b2.toString());
236                         dependencies.add(GraphCompiler.read(graphFile));
237                 }
238                 
239                 File bundleFile = FileLocator.getBundleFile(b);
240                 if(bundleFile.isDirectory()) {
241                         File folder = new File(bundleFile, "dynamicGraph");
242                         for(File f : folder.listFiles(new FilenameFilter() {
243                                 
244                                 @Override
245                                 public boolean accept(File dir, String name) {
246                                         return name.endsWith(".pgraph");
247                                 }
248                                 
249                         })) {
250                                 sources.add(new FileSource(f));
251                         }
252                 }               
253                 
254 //              System.out.println("source is " + tmpFile.getAbsolutePath());
255                 
256                 final StringBuilder errorStringBuilder = new StringBuilder();
257                 GraphCompilerPreferences prefs = new GraphCompilerPreferences();
258                 prefs.validate = true;
259                 prefs.validateRelationRestrictions = ValidationMode.ERROR;
260                 prefs.validateResourceHasType = ValidationMode.ERROR;
261                 String currentLayer0Version = OntologyVersions.getInstance().currentOntologyVersion("http://www.simantics.org/Layer0-0.0");
262                 CompilationResult result = GraphCompiler.compile(currentLayer0Version, sources, dependencies, null, prefs);
263                 
264                 for(Problem problem : result.getErrors())
265                         errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n");
266                 for(Problem problem : result.getWarnings())
267                         errorStringBuilder.append(problem.getLocation() + ": " + problem.getDescription() + "\n");
268
269                 if(errorStringBuilder.length() > 0) {
270                         LOGGER.error(errorStringBuilder.toString());
271                 } else {
272                         DataContainers.writeFile(new File(bundleFile, "graph.tg"), 
273                                         new DataContainer("graph", 1, new Variant(TransferableGraph1.BINDING, result.getGraph())));
274                 }
275                 
276         }
277         
278         /**
279          * Compile all dynamic ontologies in the Platform
280          * 
281          * @param collection
282          * @throws IOException
283          */
284         public static void compileAllDynamicOntologies() {
285                 for (Bundle bundle : getBundles()) {
286                         if(bundle.getEntry("dynamicGraph") != null) {
287                                 try {
288                                         File bundleFile = FileLocator.getBundleFile(bundle);
289                                         if(bundleFile.isDirectory()) {
290                                                 File tg = new File(bundleFile, "graph.tg");
291                                                 long tgLastModified = tg.lastModified();
292                                                 File folder = new File(bundleFile, "dynamicGraph");
293                                                 for(File f : folder.listFiles()) {
294                                                         if(f.isFile() && f.getName().endsWith(".pgraph") && f.lastModified() > tgLastModified) {
295                                                                 compile(bundle);
296                                                                 break;
297                                                         }
298                                                 }
299                                         }
300                                 } catch (Throwable e) {
301                                         LOGGER.error("Failed to compile dynamic ontologies in bundle " + bundle.getSymbolicName(), e);
302                                 }
303                         }
304                 }
305         }
306         
307         /**
308          * Get all graphs in the Platform
309          * 
310          * @param collection
311          * @throws IOException
312          */
313         public static void getAllGraphs(Collection<GraphBundle> collection) throws IOException {
314                 for (Bundle bundle : getBundles()) {
315                         GraphBundle entry = getGraph(bundle);
316                         if (entry!=null) collection.add(entry);
317                 }
318         }
319
320         /**
321          * Get bundle 
322          * 
323          * @param symbolicName
324          * @return bundle or <tt>null</tt> if there is no bundle or graph 
325          * @throws IOException
326          */
327         public static GraphBundle getGraph(String symbolicName) throws IOException {
328                 Bundle bundle = Platform.getBundle(symbolicName);
329                 if (bundle == null) return null;
330                 return getGraph( bundle );
331         }
332         
333         /**
334          * Read the graph in a graph bundle. Graph is read from "graph.tg" file in the root.
335          * 
336          * @param bundle
337          * @return transferable graph, or <tt>null</tt> if there is no graph in the bundle. 
338          * @throws IOException 
339          */
340         public static GraphBundleEx getGraph(Bundle bundle) throws IOException {
341                 URL url = bundle.getEntry("graph.tg");
342                 
343                 if (url==null) return null;
344                 InputStream is = url.openStream(); 
345                 // NOTE: this is vital for performance.
346                 is = new BufferedInputStream(is, 128*1024);
347                 try {
348                         DataInput dis = new DataInputStream(is);
349                         // or
350                         // dis = new InputStreamReadable(is, <max limit>) to protect from OOM
351                         
352                         org.simantics.databoard.container.DataContainer container = 
353                                         DataContainers.readFile(dis); 
354
355                         Binding binding = TransferableGraph1.BINDING;
356                         TransferableGraph1 graph = (TransferableGraph1)container.content.getValue(binding);
357 //                      TransferableGraph1 graph = (TransferableGraph1) Files.readFile(is, binding);
358 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): read transferable graph in " + (System.nanoTime()-start)*1e-6 + "ms");
359                         org.osgi.framework.Version osgiVersion = bundle.getVersion();
360                         Version p2Version = Version.createOSGi(osgiVersion.getMajor(), osgiVersion.getMinor(), osgiVersion.getMicro(), osgiVersion.getQualifier());
361                         String id = bundle.getSymbolicName();
362                         VersionedId vid = new VersionedId(id, p2Version);
363                         String name = (String) bundle.getHeaders().get("Bundle-Name");
364                         if (name == null) name = id;
365                         String immutable = (String) bundle.getHeaders().get("Immutable");
366                         boolean isImmutable = 
367                                         immutable != null ? 
368                                                         "true".equals(immutable) : 
369                                                                 true;
370
371 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): before hashcode calculation in " + (System.nanoTime()-start)*1e-6 + "ms");
372                         GraphBundleEx entry = new GraphBundleEx(name, graph, vid, isImmutable);
373 //                      System.out.println("getGraph(" + bundle.getSymbolicName() + "): completed in " + (System.nanoTime()-start)*1e-6 + "ms");
374                         return entry;
375                 } catch (Exception e) {
376                         throw new IOException("Problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
377                 } catch (Error e) {
378                         LOGGER.error("Serious problem loading graph.tg from bundle " + bundle.getSymbolicName(), e);
379                         throw e;
380                 } finally {
381                         is.close();
382                 }
383         }
384
385         public static class TGInfo {
386                 public Bundle bundle;
387                 public URL location;
388                 public IVersionedId vid;
389         }
390
391 }