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