View Javadoc

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