View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.osgi.boot.utils.internal;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.net.URI;
26  import java.net.URL;
27  import java.net.URLConnection;
28  import java.net.URLDecoder;
29  import java.util.ArrayList;
30  import java.util.Enumeration;
31  import java.util.zip.ZipFile;
32  
33  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
34  import org.eclipse.jetty.util.URIUtil;
35  import org.eclipse.jetty.util.resource.FileResource;
36  import org.eclipse.jetty.util.resource.Resource;
37  import org.osgi.framework.Bundle;
38  
39  /**
40   * DefaultFileLocatorHelper
41   * 
42   * 
43   * From a bundle to its location on the filesystem. Assumes the bundle is not a
44   * jar.
45   * 
46   * @author hmalphettes
47   */
48  public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
49  {
50  
51      // hack to locate the file-system directly from the bundle.
52      // support equinox, felix and nuxeo's osgi implementations.
53      // not tested on nuxeo and felix just yet.
54      // The url nuxeo and felix return is created directly from the File so it
55      // should work.
56      private static Field BUNDLE_ENTRY_FIELD = null;
57  
58      private static Field FILE_FIELD = null;
59  
60      private static Field BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = null;// ZipBundleFile
61  
62      // inside
63      // DirZipBundleEntry
64  
65      private static Field ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = null;// ZipFile
66      
67      private static final String[] FILE_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry","org.eclipse.osgi.storage.bundlefile.FileBundleEntry"};
68      private static final String[] ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry","org.eclipse.osgi.storage.bundlefile.ZipBundleEntry"};
69      private static final String[] DIR_ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry","org.eclipse.osgi.storage.bundlefile.DirZipBundleEntry"};
70      private static final String[] BUNDLE_URL_CONNECTION_CLASSES = {"org.eclipse.osgi.framework.internal.core.BundleURLConnection", "org.eclipse.osgi.storage.url.BundleURLConnection"};
71  
72  
73      public static boolean match (String name, String... names)
74      {
75          if (name == null || names == null)
76              return false;
77          boolean matched = false;
78          for (int i=0; i< names.length && !matched; i++)
79              if (name.equals(names[i]))
80                  matched = true;
81          return matched;
82      }
83      
84      
85      /**
86       * Works with equinox, felix, nuxeo and probably more. Not exactly in the
87       * spirit of OSGi but quite necessary to support self-contained webapps and
88       * other situations.
89       * 
90       * @param bundle The bundle
91       * @return Its installation location as a file.
92       * @throws Exception
93       */
94      public File getBundleInstallLocation(Bundle bundle) throws Exception
95      {
96          // String installedBundles = System.getProperty("osgi.bundles");
97          // grab the MANIFEST.MF's url
98          // and then do what it takes.
99          URL url = bundle.getEntry("/META-INF/MANIFEST.MF");
100 
101         if (url.getProtocol().equals("file"))
102         {
103             // some osgi frameworks do use the file protocole directly in some
104             // situations. Do use the FileResource to transform the URL into a
105             // File: URL#toURI is broken
106             return new FileResource(url).getFile().getParentFile().getParentFile();
107         }
108         else if (url.getProtocol().equals("bundleentry"))
109         {
110             // say hello to equinox who has its own protocol.
111             // we use introspection like there is no tomorrow to get access to
112             // the File
113 
114             URLConnection con = url.openConnection();
115             con.setUseCaches(Resource.getDefaultUseCaches()); // work around
116             // problems where
117             // url connections
118             // cache
119             // references to
120             // jars
121 
122             if (BUNDLE_ENTRY_FIELD == null)
123             {
124                 BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
125                 BUNDLE_ENTRY_FIELD.setAccessible(true);
126             }
127             Object bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
128            
129             if (match(bundleEntry.getClass().getName(), FILE_BUNDLE_ENTRY_CLASSES))
130             {
131                 if (FILE_FIELD == null)
132                 {
133                     FILE_FIELD = bundleEntry.getClass().getDeclaredField("file");
134                     FILE_FIELD.setAccessible(true);
135                 }
136                 File f = (File) FILE_FIELD.get(bundleEntry);
137                 return f.getParentFile().getParentFile();
138             }
139             else if (match(bundleEntry.getClass().getName(), ZIP_BUNDLE_ENTRY_CLASSES))
140             {
141                 url = bundle.getEntry("/");
142 
143                 con = url.openConnection();
144                 con.setDefaultUseCaches(Resource.getDefaultUseCaches());
145 
146                 if (BUNDLE_ENTRY_FIELD == null)
147                 {// this one will be a DirZipBundleEntry
148                     BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
149                     BUNDLE_ENTRY_FIELD.setAccessible(true);
150                 }
151                 bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
152                 if (BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY == null)
153                 {
154                     BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = bundleEntry.getClass().getDeclaredField("bundleFile");
155                     BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.setAccessible(true);
156                 }
157                 Object zipBundleFile = BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.get(bundleEntry);
158                 if (ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE == null)
159                 {
160                     ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = zipBundleFile.getClass().getDeclaredField("zipFile");
161                     ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.setAccessible(true);
162                 }
163                 ZipFile zipFile = (ZipFile) ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.get(zipBundleFile);
164                 return new File(zipFile.getName());
165             }
166             else if (match (bundleEntry.getClass().getName(), DIR_ZIP_BUNDLE_ENTRY_CLASSES))
167             {
168                 // that will not happen as we did ask for the manifest not a
169                 // directory.
170             }
171         }
172         else if ("bundle".equals(url.getProtocol()))
173         {
174             // observed this on felix-2.0.0
175             String location = bundle.getLocation();
176             if (location.startsWith("file:/"))
177             {
178                 URI uri = new URI(URIUtil.encodePath(location));
179                 return new File(uri);
180             }
181             else if (location.startsWith("file:"))
182             {
183                 // location defined in the BundleArchive m_bundleArchive
184                 // it is relative to relative to the BundleArchive's
185                 // m_archiveRootDir
186                 File res = new File(location.substring("file:".length()));
187                 if (!res.exists()) { return null;
188                 // Object bundleArchive = getFelixBundleArchive(bundle);
189                 // File archiveRoot =
190                 // getFelixBundleArchiveRootDir(bundleArchive);
191                 // String currentLocation =
192                 // getFelixBundleArchiveCurrentLocation(bundleArchive);
193                 // System.err.println("Got the archive root " +
194                 // archiveRoot.getAbsolutePath()
195                 // + " current location " + currentLocation +
196                 // " is directory ?");
197                 // res = new File(archiveRoot, currentLocation != null
198                 // ? currentLocation : location.substring("file:".length()));
199                 }
200                 return res;
201             }
202             else if (location.startsWith("reference:file:"))
203             {
204                 location = URLDecoder.decode(location.substring("reference:".length()), "UTF-8");
205                 File file = new File(location.substring("file:".length()));
206                 return file;
207             }
208         }
209         return null;
210     }
211 
212     /**
213      * Locate a file inside a bundle.
214      * 
215      * @param bundle
216      * @param path
217      * @return file object
218      * @throws Exception
219      */
220     public File getFileInBundle(Bundle bundle, String path) throws Exception
221     {
222         if (path != null && path.length() > 0 && path.charAt(0) == '/')
223         {
224             path = path.substring(1);
225         }
226         File bundleInstall = getBundleInstallLocation(bundle);
227         File webapp = path != null && path.length() != 0 ? new File(bundleInstall, path) : bundleInstall;
228         if (!webapp.exists()) { throw new IllegalArgumentException("Unable to locate " + path
229                                                                    + " inside "
230                                                                    + bundle.getSymbolicName()
231                                                                    + " ("
232                                                                    + (bundleInstall != null ? bundleInstall.getAbsolutePath() : " no_bundle_location ")
233                                                                    + ")"); }
234         return webapp;
235     }
236 
237     /**
238      * Helper method equivalent to Bundle#getEntry(String entryPath) except that
239      * it searches for entries in the fragments by using the Bundle#findEntries
240      * method.
241      * 
242      * @param bundle
243      * @param entryPath
244      * @return null or all the entries found for that path.
245      */
246     public Enumeration<URL> findEntries(Bundle bundle, String entryPath)
247     {
248         int last = entryPath.lastIndexOf('/');
249         String path = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(0, last) : "/";
250         if (!path.startsWith("/"))
251         {
252             path = "/" + path;
253         }
254         String pattern = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(last + 1) : entryPath;
255         Enumeration<URL> enUrls = bundle.findEntries(path, pattern, false);
256         return enUrls;
257     }
258 
259     /**
260      * If the bundle is a jar, returns the jar. If the bundle is a folder, look
261      * inside it and search for jars that it returns.
262      * <p>
263      * Good enough for our purpose (TldLocationsCache when it scans for tld
264      * files inside jars alone. In fact we only support the second situation for
265      * development purpose where the bundle was imported in pde and the classes
266      * kept in a jar.
267      * </p>
268      * 
269      * @param bundle
270      * @return The jar(s) file that is either the bundle itself, either the jars
271      *         embedded inside it.
272      */
273     public File[] locateJarsInsideBundle(Bundle bundle) throws Exception
274     {
275         File jasperLocation = getBundleInstallLocation(bundle);
276         if (jasperLocation.isDirectory())
277         {
278             // try to find the jar files inside this folder
279             ArrayList<File> urls = new ArrayList<File>();
280             for (File f : jasperLocation.listFiles())
281             {
282                 if (f.getName().endsWith(".jar") && f.isFile())
283                 {
284                     urls.add(f);
285                 }
286                 else if (f.isDirectory() && f.getName().equals("lib"))
287                 {
288                     for (File f2 : jasperLocation.listFiles())
289                     {
290                         if (f2.getName().endsWith(".jar") && f2.isFile())
291                         {
292                             urls.add(f2);
293                         }
294                     }
295                 }
296             }
297             return urls.toArray(new File[urls.size()]);
298         }
299         else
300         {
301             return new File[] { jasperLocation };
302         }
303     }
304 
305     // introspection on equinox to invoke the getLocalURL method on
306     // BundleURLConnection
307     // equivalent to using the FileLocator without depending on an equinox
308     // class.
309     private static Method BUNDLE_URL_CONNECTION_getLocalURL = null;
310 
311     private static Method BUNDLE_URL_CONNECTION_getFileURL = null;
312 
313     /**
314      * Only useful for equinox: on felix we get the file:// or jar:// url
315      * already. Other OSGi implementations have not been tested
316      * <p>
317      * Get a URL to the bundle entry that uses a common protocol (i.e. file:
318      * jar: or http: etc.).
319      * </p>
320      * 
321      * @return a URL to the bundle entry that uses a common protocol
322      */
323     public URL getLocalURL(URL url)
324     throws Exception
325     {
326         if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
327         {
328 
329             URLConnection conn = url.openConnection();
330             conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
331             if (BUNDLE_URL_CONNECTION_getLocalURL == null && match(conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
332             {
333                 BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null);
334                 BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true);
335             }
336             if (BUNDLE_URL_CONNECTION_getLocalURL != null) { return (URL) BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); }
337         }
338         return url;
339     }
340 
341     /**
342      * Only useful for equinox: on felix we get the file:// url already. Other
343      * OSGi implementations have not been tested
344      * <p>
345      * Get a URL to the content of the bundle entry that uses the file:
346      * protocol. The content of the bundle entry may be downloaded or extracted
347      * to the local file system in order to create a file: URL.
348      * 
349      * @return a URL to the content of the bundle entry that uses the file:
350      *         protocol
351      *         </p>
352      * @throws IOException 
353      */
354     public URL getFileURL(URL url) throws Exception
355  
356     {
357         if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
358         {
359 
360             URLConnection conn = url.openConnection();
361             conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
362             if (BUNDLE_URL_CONNECTION_getFileURL == null 
363                 && 
364                 match (conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
365             {
366                 BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null);
367                 BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true);
368             }
369             if (BUNDLE_URL_CONNECTION_getFileURL != null) { return (URL) BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); }
370 
371         }
372         return url;
373     }
374 
375 }