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