View Javadoc

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