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.util.ArrayList;
22  import java.util.zip.ZipFile;
23  
24  import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
25  import org.eclipse.jetty.util.URIUtil;
26  import org.osgi.framework.Bundle;
27  
28  /**
29   * From a bundle to its location on the filesystem. Assumes the bundle is not a
30   * jar.
31   * 
32   * @author hmalphettes
33   */
34  public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
35  {
36  
37      // hack to locate the file-system directly from the bundle.
38      // support equinox, felix and nuxeo's osgi implementations.
39      // not tested on nuxeo and felix just yet.
40      // The url nuxeo and felix return is created directly from the File so it
41      // should work.
42      private static Field BUNDLE_ENTRY_FIELD = null;
43      private static Field FILE_FIELD = null;
44  
45      private static Field BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = null;// ZipBundleFile
46                                                                             // inside
47                                                                             // DirZipBundleEntry
48  
49      private static Field ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = null;// ZipFile
50  
51      /**
52       * Works with equinox, felix, nuxeo and probably more. Not exactly in the
53       * spirit of OSGi but quite necessary to support self-contained webapps and
54       * other situations.
55       * <p>
56       * Currently only works with bundles that are not jar.
57       * </p>
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() + " " +
71          // url.getProtocol());
72          if (url.getProtocol().equals("file"))
73          {
74              // some osgi frameworks do use the file protocole directly in some
75              // situations
76              return new File(url.toURI()).getParentFile().getParentFile();
77          }
78          else if (url.getProtocol().equals("bundleentry"))
79          {
80              // say hello to equinox who has its own protocol.
81              // we use introspection like there is no tomorrow to get access to
82              // the File
83              URLConnection con = url.openConnection();
84              if (BUNDLE_ENTRY_FIELD == null)
85              {
86                  BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
87                  BUNDLE_ENTRY_FIELD.setAccessible(true);
88              }
89              Object bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
90              if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry"))
91              {
92                  if (FILE_FIELD == null)
93                  {
94                      FILE_FIELD = bundleEntry.getClass().getDeclaredField("file");
95                      FILE_FIELD.setAccessible(true);
96                  }
97                  File f = (File)FILE_FIELD.get(bundleEntry);
98                  return f.getParentFile().getParentFile();
99              }
100             else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry"))
101             {
102                 url = bundle.getEntry("/");
103                 con = url.openConnection();
104                 if (BUNDLE_ENTRY_FIELD == null)
105                 {// this one will be a DirZipBundleEntry
106                     BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
107                     BUNDLE_ENTRY_FIELD.setAccessible(true);
108                 }
109                 bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
110                 if (BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY == null)
111                 {
112                     BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = bundleEntry.getClass().getDeclaredField("bundleFile");
113                     BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.setAccessible(true);
114                 }
115                 Object zipBundleFile = BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.get(bundleEntry);
116                 if (ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE == null)
117                 {
118                     ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = zipBundleFile.getClass().getDeclaredField("zipFile");
119                     ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.setAccessible(true);
120                 }
121                 ZipFile zipFile = (ZipFile)ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.get(zipBundleFile);
122                 return new File(zipFile.getName());
123             }
124             else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry"))
125             {
126                 // that will not happen as we did ask for the manifest not a
127                 // directory.
128             }
129         }
130         else if ("bundle".equals(url.getProtocol()))
131         {
132             // observed this on felix-2.0.0
133             String location = bundle.getLocation();
134             if (location.startsWith("file:/"))
135             {
136                 URI uri = new URI(URIUtil.encodePath(location));
137                 return new File(uri);
138             }
139         }
140         return null;
141     }
142 
143     /**
144      * Locate a file inside a bundle.
145      * 
146      * @param bundle
147      * @param path
148      * @return
149      * @throws Exception
150      */
151     public File getFileInBundle(Bundle bundle, String path) throws Exception
152     {
153         if (path != null && path.length() > 0 && path.charAt(0) == '/')
154         {
155             path = path.substring(1);
156         }
157         File bundleInstall = getBundleInstallLocation(bundle);
158         File webapp = path != null && path.length() != 0?new File(bundleInstall,path):bundleInstall;
159         if (!webapp.exists())
160         {
161             throw new IllegalArgumentException("Unable to locate " + path + " inside " + bundle.getSymbolicName() + " ("
162                     + (bundleInstall != null?bundleInstall.getAbsolutePath():" no_bundle_location ") + ")");
163         }
164         return webapp;
165     }
166 
167     /**
168      * If the bundle is a jar, returns the jar. If the bundle is a folder, look
169      * inside it and search for jars that it returns.
170      * <p>
171      * Good enough for our purpose (TldLocationsCache when it scans for tld
172      * files inside jars alone. In fact we only support the second situation for
173      * development purpose where the bundle was imported in pde and the classes
174      * kept in a jar.
175      * </p>
176      * 
177      * @param bundle
178      * @return The jar(s) file that is either the bundle itself, either the jars
179      *         embedded inside it.
180      */
181     public File[] locateJarsInsideBundle(Bundle bundle) throws Exception
182     {
183         File jasperLocation = getBundleInstallLocation(bundle);
184         if (jasperLocation.isDirectory())
185         {
186             // try to find the jar files inside this folder
187             ArrayList<File> urls = new ArrayList<File>();
188             for (File f : jasperLocation.listFiles())
189             {
190                 if (f.getName().endsWith(".jar") && f.isFile())
191                 {
192                     urls.add(f);
193                 }
194                 else if (f.isDirectory() && f.getName().equals("lib"))
195                 {
196                     for (File f2 : jasperLocation.listFiles())
197                     {
198                         if (f2.getName().endsWith(".jar") && f2.isFile())
199                         {
200                             urls.add(f2);
201                         }
202                     }
203                 }
204             }
205             return urls.toArray(new File[urls.size()]);
206         }
207         else
208         {
209             return new File[]
210             { jasperLocation };
211         }
212     }
213     
214 
215 	//introspection on equinox to invoke the getLocalURL method on BundleURLConnection
216 	private static Method BUNDLE_URL_CONNECTION_getLocalURL = null;
217 	private static Method BUNDLE_URL_CONNECTION_getFileURL = null;
218 	/**
219 	 * Only useful for equinox: on felix we get the file:// or jar:// url already.
220 	 * Other OSGi implementations have not been tested
221 	 * <p>
222 	 * Get a URL to the bundle entry that uses a common protocol (i.e. file:
223 	 * jar: or http: etc.).  
224 	 * </p>
225 	 * @return a URL to the bundle entry that uses a common protocol
226 	 */
227 	public static URL getLocalURL(URL url) {
228 		if ("bundleresource".equals(url.getProtocol())) {
229 			try {
230 				URLConnection conn = url.openConnection();
231 				if (BUNDLE_URL_CONNECTION_getLocalURL == null && 
232 						conn.getClass().getName().equals(
233 								"org.eclipse.osgi.framework.internal.core.BundleURLConnection")) {
234 					BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null);
235 					BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true);
236 				}
237 				if (BUNDLE_URL_CONNECTION_getLocalURL != null) {
238 					return (URL)BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null);
239 				}
240 			} catch (Throwable t) {
241 				t.printStackTrace();
242 			}
243 		}
244 		return url;
245 	}
246 	/**
247 	 * Only useful for equinox: on felix we get the file:// url already.
248 	 * Other OSGi implementations have not been tested
249 	 * <p>
250 	 * Get a URL to the content of the bundle entry that uses the file: protocol.
251 	 * The content of the bundle entry may be downloaded or extracted to the local
252 	 * file system in order to create a file: URL.
253 	 * @return a URL to the content of the bundle entry that uses the file: protocol
254 	 * </p>
255 	 */
256 	public static URL getFileURL(URL url)
257 	{
258 		if ("bundleresource".equals(url.getProtocol()))
259 		{
260 			try
261 			{
262 				URLConnection conn = url.openConnection();
263 				if (BUNDLE_URL_CONNECTION_getFileURL == null && 
264 						conn.getClass().getName().equals(
265 								"org.eclipse.osgi.framework.internal.core.BundleURLConnection"))
266 				{
267 					BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null);
268 					BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true);
269 				}
270 				if (BUNDLE_URL_CONNECTION_getFileURL != null)
271 				{
272 					return (URL)BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null);
273 				}
274 			}
275 			catch (Throwable t)
276 			{
277 				t.printStackTrace();
278 			}
279 		}
280 		return url;
281 	}
282 
283 }