blob: a2ad48bded7176ab97b90df3f003b8d732b8e319 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.osgi.boot.utils.internal;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipFile;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.osgi.framework.Bundle;
/**
* DefaultFileLocatorHelper
* <p>
* From a bundle to its location on the filesystem. Assumes the bundle is not a
* jar.
*/
public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
{
// hack to locate the file-system directly from the bundle.
// support equinox, felix and nuxeo's osgi implementations.
// not tested on nuxeo and felix just yet.
// The url nuxeo and felix return is created directly from the File so it
// should work.
private static Field BUNDLE_ENTRY_FIELD = null;
private static Field FILE_FIELD = null;
private static Field BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = null;// ZipBundleFile
// inside
// DirZipBundleEntry
private static Field ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = null;// ZipFile
private static final String[] FILE_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry","org.eclipse.osgi.storage.bundlefile.FileBundleEntry"};
private static final String[] ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry","org.eclipse.osgi.storage.bundlefile.ZipBundleEntry"};
private static final String[] DIR_ZIP_BUNDLE_ENTRY_CLASSES = {"org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry","org.eclipse.osgi.storage.bundlefile.DirZipBundleEntry"};
private static final String[] BUNDLE_URL_CONNECTION_CLASSES = {"org.eclipse.osgi.framework.internal.core.BundleURLConnection", "org.eclipse.osgi.storage.url.BundleURLConnection"};
public static boolean match (String name, String... names)
{
if (name == null || names == null)
return false;
boolean matched = false;
for (int i=0; i< names.length && !matched; i++)
if (name.equals(names[i]))
matched = true;
return matched;
}
/**
* Works with equinox, felix, nuxeo and probably more. Not exactly in the
* spirit of OSGi but quite necessary to support self-contained webapps and
* other situations.
*
* @param bundle The bundle
* @return Its installation location as a file.
* @throws Exception if unable to get the bundle install location
*/
public File getBundleInstallLocation(Bundle bundle) throws Exception
{
// String installedBundles = System.getProperty("osgi.bundles");
// grab the MANIFEST.MF's url
// and then do what it takes.
URL url = bundle.getEntry("/META-INF/MANIFEST.MF");
if (url.getProtocol().equals("file"))
{
// some osgi frameworks do use the file protocol directly in some
// situations. Do use the FileResource to transform the URL into a
// File: URL#toURI is broken
return new PathResource(url).getFile().getParentFile().getParentFile();
}
else if (url.getProtocol().equals("bundleentry"))
{
// say hello to equinox who has its own protocol.
// we use introspection like there is no tomorrow to get access to
// the File
URLConnection con = url.openConnection();
con.setUseCaches(Resource.getDefaultUseCaches()); // work around
// problems where
// url connections
// cache
// references to
// jars
if (BUNDLE_ENTRY_FIELD == null)
{
BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
BUNDLE_ENTRY_FIELD.setAccessible(true);
}
Object bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
if (match(bundleEntry.getClass().getName(), FILE_BUNDLE_ENTRY_CLASSES))
{
if (FILE_FIELD == null)
{
FILE_FIELD = bundleEntry.getClass().getDeclaredField("file");
FILE_FIELD.setAccessible(true);
}
File f = (File) FILE_FIELD.get(bundleEntry);
return f.getParentFile().getParentFile();
}
else if (match(bundleEntry.getClass().getName(), ZIP_BUNDLE_ENTRY_CLASSES))
{
url = bundle.getEntry("/");
con = url.openConnection();
con.setDefaultUseCaches(Resource.getDefaultUseCaches());
if (BUNDLE_ENTRY_FIELD == null)
{// this one will be a DirZipBundleEntry
BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry");
BUNDLE_ENTRY_FIELD.setAccessible(true);
}
bundleEntry = BUNDLE_ENTRY_FIELD.get(con);
if (BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY == null)
{
BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = bundleEntry.getClass().getDeclaredField("bundleFile");
BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.setAccessible(true);
}
Object zipBundleFile = BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.get(bundleEntry);
if (ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE == null)
{
ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = zipBundleFile.getClass().getDeclaredField("zipFile");
ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.setAccessible(true);
}
ZipFile zipFile = (ZipFile) ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.get(zipBundleFile);
return new File(zipFile.getName());
}
else if (match (bundleEntry.getClass().getName(), DIR_ZIP_BUNDLE_ENTRY_CLASSES))
{
// that will not happen as we did ask for the manifest not a
// directory.
}
}
else if ("bundle".equals(url.getProtocol()))
{
// observed this on felix-2.0.0
String location = bundle.getLocation();
if (location.startsWith("file:/"))
{
URI uri = new URI(URIUtil.encodePath(location));
return new File(uri);
}
else if (location.startsWith("file:"))
{
// location defined in the BundleArchive m_bundleArchive
// it is relative to relative to the BundleArchive's
// m_archiveRootDir
File res = new File(location.substring("file:".length()));
if (!res.exists()) { return null;
// Object bundleArchive = getFelixBundleArchive(bundle);
// File archiveRoot =
// getFelixBundleArchiveRootDir(bundleArchive);
// String currentLocation =
// getFelixBundleArchiveCurrentLocation(bundleArchive);
// System.err.println("Got the archive root " +
// archiveRoot.getAbsolutePath()
// + " current location " + currentLocation +
// " is directory ?");
// res = new File(archiveRoot, currentLocation != null
// ? currentLocation : location.substring("file:".length()));
}
return res;
}
else if (location.startsWith("reference:file:"))
{
location = URLDecoder.decode(location.substring("reference:".length()), "UTF-8");
File file = new File(location.substring("file:".length()));
return file;
}
}
return null;
}
/**
* Locate a file inside a bundle.
*
* @param bundle the bundle
* @param path the path
* @return file object
* @throws Exception if unable to get the file in the bundle
*/
public File getFileInBundle(Bundle bundle, String path) throws Exception
{
if (path != null && path.length() > 0 && path.charAt(0) == '/')
{
path = path.substring(1);
}
File bundleInstall = getBundleInstallLocation(bundle);
File webapp = path != null && path.length() != 0 ? new File(bundleInstall, path) : bundleInstall;
if (!webapp.exists()) { throw new IllegalArgumentException("Unable to locate " + path
+ " inside "
+ bundle.getSymbolicName()
+ " ("
+ (bundleInstall != null ? bundleInstall.getAbsolutePath() : " no_bundle_location ")
+ ")"); }
return webapp;
}
/**
* Helper method equivalent to Bundle#getEntry(String entryPath) except that
* it searches for entries in the fragments by using the Bundle#findEntries
* method.
*
* @param bundle the bundle
* @param entryPath the entry path
* @return null or all the entries found for that path.
*/
public Enumeration<URL> findEntries(Bundle bundle, String entryPath)
{
int last = entryPath.lastIndexOf('/');
String path = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(0, last) : "/";
if (!path.startsWith("/"))
{
path = "/" + path;
}
String pattern = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(last + 1) : entryPath;
Enumeration<URL> enUrls = bundle.findEntries(path, pattern, false);
return enUrls;
}
/**
* If the bundle is a jar, returns the jar. If the bundle is a folder, look
* inside it and search for jars that it returns.
* <p>
* Good enough for our purpose (TldLocationsCache when it scans for tld
* files inside jars alone. In fact we only support the second situation for
* development purpose where the bundle was imported in pde and the classes
* kept in a jar.
* </p>
*
* @param bundle the bundle
* @return The jar(s) file that is either the bundle itself, either the jars
* embedded inside it.
*/
public File[] locateJarsInsideBundle(Bundle bundle) throws Exception
{
File jasperLocation = getBundleInstallLocation(bundle);
if (jasperLocation.isDirectory())
{
// try to find the jar files inside this folder
ArrayList<File> urls = new ArrayList<File>();
for (File f : jasperLocation.listFiles())
{
if (f.getName().endsWith(".jar") && f.isFile())
{
urls.add(f);
}
else if (f.isDirectory() && f.getName().equals("lib"))
{
for (File f2 : jasperLocation.listFiles())
{
if (f2.getName().endsWith(".jar") && f2.isFile())
{
urls.add(f2);
}
}
}
}
return urls.toArray(new File[urls.size()]);
}
else
{
return new File[] { jasperLocation };
}
}
// introspection on equinox to invoke the getLocalURL method on
// BundleURLConnection
// equivalent to using the FileLocator without depending on an equinox
// class.
private static Method BUNDLE_URL_CONNECTION_getLocalURL = null;
private static Method BUNDLE_URL_CONNECTION_getFileURL = null;
/**
* Only useful for equinox: on felix we get the file:// or jar:// url
* already. Other OSGi implementations have not been tested
* <p>
* Get a URL to the bundle entry that uses a common protocol (i.e. file:
* jar: or http: etc.).
* </p>
*
* @return a URL to the bundle entry that uses a common protocol
*/
public URL getLocalURL(URL url)
throws Exception
{
if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
{
URLConnection conn = url.openConnection();
conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
if (BUNDLE_URL_CONNECTION_getLocalURL == null && match(conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
{
BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null);
BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true);
}
if (BUNDLE_URL_CONNECTION_getLocalURL != null) { return (URL) BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); }
}
return url;
}
/**
* Only useful for equinox: on felix we get the file:// url already. Other
* OSGi implementations have not been tested
* <p>
* Get a URL to the content of the bundle entry that uses the file:
* protocol. The content of the bundle entry may be downloaded or extracted
* to the local file system in order to create a file: URL.
*
* @return a URL to the content of the bundle entry that uses the file:
* protocol
* </p>
* @throws Exception if unable to get the file url
*/
public URL getFileURL(URL url) throws Exception
{
if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol()))
{
URLConnection conn = url.openConnection();
conn.setDefaultUseCaches(Resource.getDefaultUseCaches());
if (BUNDLE_URL_CONNECTION_getFileURL == null
&&
match (conn.getClass().getName(), BUNDLE_URL_CONNECTION_CLASSES))
{
BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null);
BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true);
}
if (BUNDLE_URL_CONNECTION_getFileURL != null) { return (URL) BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); }
}
return url;
}
}