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