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