View Javadoc

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