blob: 4b6a0c96334eccba3ad0343a1fa7c23804f6ab09 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cognos Incorporated - initial API and implementation
* IBM Corporation - bug fixes and enhancements
* Code 9 - bug fixes and enhancements
*******************************************************************************/
package org.eclipse.equinox.servletbridge;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.util.*;
import java.util.jar.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
/**
* The FrameworkLauncher provides the logic to:
* 1) init
* 2) deploy
* 3) start
* 4) stop
* 5) undeploy
* 6) destroy
* an instance of the OSGi framework.
* These 6 methods are provided to help manage the life-cycle and are called from outside this
* class by the BridgeServlet. To create an extended FrameworkLauncher over-ride these methods to allow
* custom behavior.
*/
public class FrameworkLauncher {
private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
private static final String CONFIG_INI = "config.ini"; //$NON-NLS-1$
private static final String DOT_JAR = ".jar"; //$NON-NLS-1$
private static final String WS_DELIM = " \t\n\r\f"; //$NON-NLS-1$
protected static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
protected static final String FRAMEWORK_BUNDLE_NAME = "org.eclipse.osgi"; //$NON-NLS-1$
protected static final String STARTER = "org.eclipse.core.runtime.adaptor.EclipseStarter"; //$NON-NLS-1$
protected static final String NULL_IDENTIFIER = "@null"; //$NON-NLS-1$
protected static final String OSGI_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
protected static final String OSGI_FRAMEWORK_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
protected static final String OSGI_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$
protected static final String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; //$NON-NLS-1$
protected static final String OSGI_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
protected static final String OSGI_FORCED_RESTART = "osgi.forcedRestart"; //$NON-NLS-1$
protected static final String RESOURCE_BASE = "/WEB-INF/"; //$NON-NLS-1$
protected static final String ECLIPSE = "eclipse/"; //$NON-NLS-1$
protected static final String LAUNCH_INI = "launch.ini"; //$NON-NLS-1$
private static final String EXTENSIONBUNDLE_DEFAULT_BSN = "org.eclipse.equinox.servletbridge.extensionbundle"; //$NON-NLS-1$
private static final String EXTENSIONBUNDLE_DEFAULT_VERSION = "1.3.0"; //$NON-NLS-1$
private static final String MANIFEST_VERSION = "Manifest-Version"; //$NON-NLS-1$
private static final String BUNDLE_MANIFEST_VERSION = "Bundle-ManifestVersion"; //$NON-NLS-1$
private static final String BUNDLE_NAME = "Bundle-Name"; //$NON-NLS-1$
private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; //$NON-NLS-1$
private static final String BUNDLE_VERSION = "Bundle-Version"; //$NON-NLS-1$
private static final String FRAGMENT_HOST = "Fragment-Host"; //$NON-NLS-1$
private static final String EXPORT_PACKAGE = "Export-Package"; //$NON-NLS-1$
private static final String CONFIG_COMMANDLINE = "commandline"; //$NON-NLS-1$
private static final String CONFIG_EXTENDED_FRAMEWORK_EXPORTS = "extendedFrameworkExports"; //$NON-NLS-1$
private static final String CONFIG_OVERRIDE_AND_REPLACE_EXTENSION_BUNDLE = "overrideAndReplaceExtensionBundle"; //$NON-NLS-1$
static final PermissionCollection allPermissions = new PermissionCollection() {
private static final long serialVersionUID = 482874725021998286L;
// The AllPermission permission
Permission allPermission = new AllPermission();
// A simple PermissionCollection that only has AllPermission
@Override
public void add(Permission permission) {
// do nothing
}
@Override
public boolean implies(Permission permission) {
return true;
}
@Override
public Enumeration<Permission> elements() {
return new Enumeration<Permission>() {
int cur = 0;
@Override
public boolean hasMoreElements() {
return cur < 1;
}
@Override
public Permission nextElement() {
if (cur == 0) {
cur = 1;
return allPermission;
}
throw new NoSuchElementException();
}
};
}
};
static {
// We do this to ensure the anonymous Enumeration class in allPermissions is pre-loaded
if (allPermissions.elements() == null)
throw new IllegalStateException();
}
protected ServletConfig config;
protected ServletContext context;
protected String resourceBase;
private File platformDirectory;
private ClassLoader frameworkContextClassLoader;
private CloseableURLClassLoader frameworkClassLoader;
void init(ServletConfig servletConfig) {
config = servletConfig;
context = servletConfig.getServletContext();
initResourceBase();
init();
}
/**
* try to find the resource base for this webapp by looking for the launcher initialization file.
*/
protected void initResourceBase() {
try {
if (context.getResource(RESOURCE_BASE + LAUNCH_INI) != null) {
resourceBase = RESOURCE_BASE;
return;
}
if (context.getResource(RESOURCE_BASE + ECLIPSE + LAUNCH_INI) != null) {
resourceBase = RESOURCE_BASE + ECLIPSE;
return;
}
} catch (MalformedURLException e) {
// ignore
}
// If things don't work out, use the default resource base
resourceBase = RESOURCE_BASE;
}
/**
* init is the first method called on the FrameworkLauncher and can be used for any initial setup.
* The default behavior is to do nothing.
*/
public void init() {
// do nothing for now
}
/**
* destroy is the last method called on the FrameworkLauncher and can be used for any final cleanup.
* The default behavior is to do nothing.
*/
public void destroy() {
// do nothing for now
}
/**
* deploy is used to move the OSGi framework libraries into a location suitable for execution.
* The default behavior is to copy the contents of the webapp's WEB-INF/eclipse directory
* to the webapp's temp directory.
*/
public synchronized void deploy() {
if (platformDirectory != null) {
context.log("Framework is already deployed"); //$NON-NLS-1$
return;
}
File servletTemp = (File) context.getAttribute("javax.servlet.context.tempdir"); //$NON-NLS-1$
platformDirectory = new File(servletTemp, "eclipse"); //$NON-NLS-1$
if (!platformDirectory.exists()) {
platformDirectory.mkdirs();
}
copyResource(resourceBase + "configuration/", new File(platformDirectory, "configuration")); //$NON-NLS-1$ //$NON-NLS-2$
copyResource(resourceBase + "features/", new File(platformDirectory, "features")); //$NON-NLS-1$ //$NON-NLS-2$
File plugins = new File(platformDirectory, "plugins"); //$NON-NLS-1$
copyResource(resourceBase + "plugins/", plugins); //$NON-NLS-1$
copyResource(resourceBase + "p2/", new File(platformDirectory, "p2")); //$NON-NLS-1$ //$NON-NLS-2$
deployExtensionBundle(plugins);
copyResource(resourceBase + ".eclipseproduct", new File(platformDirectory, ".eclipseproduct")); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* deployExtensionBundle will generate the Servletbridge extensionbundle if it is not already present in the platform's
* plugin directory. By default it exports "org.eclipse.equinox.servletbridge" and a versioned export of the Servlet API.
* Additional exports can be added by using the "extendedFrameworkExports" initial-param in the ServletConfig
*/
private void deployExtensionBundle(File plugins) {
// we might want to parameterize the extension bundle BSN in the future
final String extensionBundleBSN = EXTENSIONBUNDLE_DEFAULT_BSN;
File extensionBundleFile = findExtensionBundleFile(plugins, extensionBundleBSN);
if (extensionBundleFile == null)
generateExtensionBundle(plugins, extensionBundleBSN, EXTENSIONBUNDLE_DEFAULT_VERSION);
else if (Boolean.valueOf(config.getInitParameter(CONFIG_OVERRIDE_AND_REPLACE_EXTENSION_BUNDLE)).booleanValue()) {
String extensionBundleVersion = findExtensionBundleVersion(extensionBundleFile, extensionBundleBSN);
if (extensionBundleFile.isDirectory()) {
deleteDirectory(extensionBundleFile);
} else {
extensionBundleFile.delete();
}
generateExtensionBundle(plugins, extensionBundleBSN, extensionBundleVersion);
} else {
processExtensionBundle(extensionBundleFile);
}
}
private File findExtensionBundleFile(File plugins, final String extensionBundleBSN) {
FileFilter extensionBundleFilter = new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().startsWith(extensionBundleBSN + "_"); //$NON-NLS-1$
}
};
File[] extensionBundles = plugins.listFiles(extensionBundleFilter);
if (extensionBundles.length == 0)
return null;
if (extensionBundles.length > 1) {
for (int i = 1; i < extensionBundles.length; i++) {
if (extensionBundles[i].isDirectory()) {
deleteDirectory(extensionBundles[i]);
} else {
extensionBundles[i].delete();
}
}
}
return extensionBundles[0];
}
private String findExtensionBundleVersion(File extensionBundleFile, String extensionBundleBSN) {
String fileName = extensionBundleFile.getName();
if (fileName.endsWith(DOT_JAR)) {
return fileName.substring(extensionBundleBSN.length() + 1, fileName.length() - DOT_JAR.length());
}
return fileName.substring(extensionBundleBSN.length() + 1);
}
private void generateExtensionBundle(File plugins, String extensionBundleBSN, String extensionBundleVersion) {
Manifest mf = new Manifest();
Attributes attribs = mf.getMainAttributes();
attribs.putValue(MANIFEST_VERSION, "1.0"); //$NON-NLS-1$
attribs.putValue(BUNDLE_MANIFEST_VERSION, "2"); //$NON-NLS-1$
attribs.putValue(BUNDLE_NAME, "Servletbridge Extension Bundle"); //$NON-NLS-1$
attribs.putValue(BUNDLE_SYMBOLIC_NAME, extensionBundleBSN);
attribs.putValue(BUNDLE_VERSION, extensionBundleVersion);
attribs.putValue(FRAGMENT_HOST, "system.bundle; extension:=framework"); //$NON-NLS-1$
String packageExports = null;
if (context.getMajorVersion() > 3) {
// we really have no idea what the packages or versions are, it all just a guess ...
String servletVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$
packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.annotation; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.descriptor; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.resources; version=" + servletVersion; //$NON-NLS-1$
} else if (context.getMajorVersion() == 3) {
// We know spec version 3.0 corresponds to package version 2.6
// we are guessing future 3.x spec versions will increment package versions minor, so ...
String servletVersion = (context.getMajorVersion() - 1) + "." + (context.getMinorVersion() + 6); //$NON-NLS-1$
packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.annotation; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.descriptor; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.resources; version=" + servletVersion; //$NON-NLS-1$
} else {
// We know spec version 2.x directly correspond to package versions
String servletVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$
packageExports = "org.eclipse.equinox.servletbridge; version=1.1" + //$NON-NLS-1$
", javax.servlet; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.http; version=" + servletVersion + //$NON-NLS-1$
", javax.servlet.resources; version=" + servletVersion; //$NON-NLS-1$
}
String extendedExports = config.getInitParameter(CONFIG_EXTENDED_FRAMEWORK_EXPORTS);
if (extendedExports != null && extendedExports.trim().length() != 0)
packageExports += ", " + extendedExports; //$NON-NLS-1$
attribs.putValue(EXPORT_PACKAGE, packageExports);
writeJarFile(new File(plugins, extensionBundleBSN + "_" + extensionBundleVersion + DOT_JAR), mf); //$NON-NLS-1$
}
private void processExtensionBundle(File extensionBundleFile) {
String fileName = extensionBundleFile.getName();
if (fileName.endsWith(DOT_JAR)) {
Manifest mf = readJarFile(extensionBundleFile);
if (mf == null)
return;
Attributes attributes = mf.getMainAttributes();
String exportPackage = (String) attributes.remove(new Attributes.Name("X-Deploy-Export-Package")); //$NON-NLS-1$
if (exportPackage != null) {
attributes.putValue("Export-Package", exportPackage); //$NON-NLS-1$
writeJarFile(extensionBundleFile, mf);
}
}
}
private void writeJarFile(File jarFile, Manifest mf) {
try {
JarOutputStream jos = null;
try {
jos = new JarOutputStream(new FileOutputStream(jarFile), mf);
jos.finish();
} finally {
if (jos != null)
jos.close();
}
} catch (IOException e) {
context.log("Error writing extension bundle", e); //$NON-NLS-1$
}
}
private Manifest readJarFile(File jarFile) {
try {
JarInputStream jis = null;
try {
jis = new JarInputStream(new FileInputStream(jarFile));
return jis.getManifest();
} finally {
if (jis != null)
jis.close();
}
} catch (IOException e) {
context.log("Error reading extension bundle", e); //$NON-NLS-1$
}
return null;
}
/** undeploy is the reverse operation of deploy and removes the OSGi framework libraries from their
* execution location. Typically this method will only be called if a manual undeploy is requested in the
* ServletBridge.
* By default, this method removes the OSGi install and also removes the workspace.
*/
public synchronized void undeploy() {
if (platformDirectory == null) {
context.log("Undeploy unnecessary. - (not deployed)"); //$NON-NLS-1$
return;
}
if (frameworkClassLoader != null) {
throw new IllegalStateException("Could not undeploy Framework - (not stopped)"); //$NON-NLS-1$
}
deleteDirectory(new File(platformDirectory, "configuration")); //$NON-NLS-1$
deleteDirectory(new File(platformDirectory, "features")); //$NON-NLS-1$
deleteDirectory(new File(platformDirectory, "plugins")); //$NON-NLS-1$
deleteDirectory(new File(platformDirectory, "workspace")); //$NON-NLS-1$
deleteDirectory(new File(platformDirectory, "p2")); //$NON-NLS-1$
new File(platformDirectory, ".eclipseproduct").delete(); //$NON-NLS-1$
platformDirectory = null;
}
/** start is used to "start" a previously deployed OSGi framework
* The default behavior will read launcher.ini to create a set of initial properties and
* use the "commandline" configuration parameter to create the equivalent command line arguments
* available when starting Eclipse.
*/
public synchronized void start() {
if (platformDirectory == null)
throw new IllegalStateException("Could not start the Framework - (not deployed)"); //$NON-NLS-1$
if (frameworkClassLoader != null) {
context.log("Framework is already started"); //$NON-NLS-1$
return;
}
Map<String, String> initialPropertyMap = buildInitialPropertyMap();
String[] args = buildCommandLineArguments();
// Handle commandline -D properties
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("-D")) { //$NON-NLS-1$
int equalsIndex = arg.indexOf('=');
if (equalsIndex == -1) {
initialPropertyMap.put(arg.substring(2), ""); //$NON-NLS-1$
} else {
String key = arg.substring(2, equalsIndex);
String value = arg.substring(equalsIndex + 1);
if (value.startsWith("\"") && value.endsWith("\"")) //$NON-NLS-1$//$NON-NLS-2$
value = value.substring(1, value.length() - 1);
setInitialProperty(initialPropertyMap, key, value);
}
}
}
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
System.setProperty("osgi.framework.useSystemProperties", "false"); //$NON-NLS-1$ //$NON-NLS-2$
URL[] frameworkURLs = findFrameworkURLs(initialPropertyMap);
frameworkClassLoader = new ChildFirstURLClassLoader(frameworkURLs, this.getClass().getClassLoader());
Class<?> clazz = frameworkClassLoader.loadClass(STARTER);
Method setInitialProperties = clazz.getMethod("setInitialProperties", Map.class); //$NON-NLS-1$
setInitialProperties.invoke(null, initialPropertyMap);
registerRestartHandler(clazz);
Method runMethod = clazz.getMethod("startup", String[].class, Runnable.class); //$NON-NLS-1$
runMethod.invoke(null, args, null);
frameworkContextClassLoader = Thread.currentThread().getContextClassLoader();
} catch (
InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t == null)
t = ite;
context.log("Error while starting Framework", t); //$NON-NLS-1$
throw new RuntimeException(t.getMessage());
} catch (Exception e) {
context.log("Error while starting Framework", e); //$NON-NLS-1$
throw new RuntimeException(e.getMessage());
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
private URL[] findFrameworkURLs(Map<String, String> initialPropertyMap) {
List<URL> frameworkURLs = new ArrayList<>();
String installArea = initialPropertyMap.get(OSGI_INSTALL_AREA);
if (installArea.startsWith(FILE_SCHEME)) {
installArea = installArea.substring(FILE_SCHEME.length());
}
File installBase = new File(installArea);
// OSGi framework
String osgiFramework = initialPropertyMap.get(OSGI_FRAMEWORK);
File osgiFrameworkFile = null;
if (osgiFramework == null) {
// search for osgi.framework in osgi.install.area
String path = new File(installBase, "plugins").toString(); //$NON-NLS-1$
path = searchFor(FRAMEWORK_BUNDLE_NAME, path);
if (path == null)
throw new RuntimeException("Could not find framework"); //$NON-NLS-1$
osgiFrameworkFile = new File(path);
} else {
if (osgiFramework.startsWith(FILE_SCHEME)) {
osgiFramework = osgiFramework.substring(FILE_SCHEME.length());
}
osgiFrameworkFile = new File(osgiFramework);
if (!osgiFrameworkFile.isAbsolute())
osgiFrameworkFile = new File(installBase, osgiFramework);
}
try {
URL frameworkURL = osgiFrameworkFile.toURL();
frameworkURLs.add(frameworkURL);
// ensure the framework URL is absolute
initialPropertyMap.put(OSGI_FRAMEWORK, frameworkURL.toExternalForm());
} catch (MalformedURLException e) {
throw new RuntimeException("Could not find framework -- " + e.getMessage()); //$NON-NLS-1$
}
// OSGi framework extensions
String osgiFrameworkExtensions = initialPropertyMap.get(OSGI_FRAMEWORK_EXTENSIONS);
if (osgiFrameworkExtensions != null) {
StringTokenizer tokenizer = new StringTokenizer(osgiFrameworkExtensions, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String extension = tokenizer.nextToken().trim();
if (extension.length() == 0)
continue;
URL extensionURL = findExtensionURL(extension, osgiFrameworkFile);
if (extensionURL != null) {
frameworkURLs.add(extensionURL);
}
}
}
return frameworkURLs.toArray(new URL[frameworkURLs.size()]);
}
private URL findExtensionURL(String extension, File osgiFrameworkFile) {
File extensionFile = null;
if (extension.startsWith(REFERENCE_SCHEME)) {
extension = extension.substring(REFERENCE_SCHEME.length());
if (!extension.startsWith(FILE_SCHEME))
throw new RuntimeException("Non-file scheme for framework extension URL -- " + extension); //$NON-NLS-1$
extension = extension.substring(FILE_SCHEME.length());
extensionFile = new File(extension);
if (!extensionFile.isAbsolute())
extensionFile = new File(osgiFrameworkFile.getParentFile(), extension);
} else {
String fullExtensionPath = searchFor(extension, osgiFrameworkFile.getParent());
if (fullExtensionPath == null)
return null;
extensionFile = new File(fullExtensionPath);
}
try {
return extensionFile.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Could not find framework extension -- " + extensionFile.getAbsolutePath() + " : " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private void registerRestartHandler(Class<?> starterClazz) throws IllegalAccessException, InvocationTargetException {
Method registerFrameworkShutdownHandler = null;
try {
registerFrameworkShutdownHandler = starterClazz.getDeclaredMethod("internalAddFrameworkShutdownHandler", Runnable.class); //$NON-NLS-1$
if (!registerFrameworkShutdownHandler.isAccessible()) {
registerFrameworkShutdownHandler.setAccessible(true);
}
Runnable restartHandler = createRestartHandler(starterClazz);
registerFrameworkShutdownHandler.invoke(null, restartHandler);
} catch (NoSuchMethodException e) {
// Ok. However we will not support restart events. Log this as info
context.log(starterClazz.getName() + " does not support setting a shutdown handler. Restart handling is disabled."); //$NON-NLS-1$
return;
}
}
private Runnable createRestartHandler(Class<?> starterClazz) throws NoSuchMethodException {
final Method getProperty = starterClazz.getDeclaredMethod("getProperty", String.class); //$NON-NLS-1$
if (!getProperty.isAccessible()) {
getProperty.setAccessible(true);
}
Runnable restartHandler = new Runnable() {
@Override
public void run() {
try {
String forcedRestart = (String) getProperty.invoke(null, OSGI_FORCED_RESTART);
if (Boolean.valueOf(forcedRestart).booleanValue()) {
stop();
start();
}
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t == null)
t = ite;
throw new RuntimeException(t.getMessage());
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
};
return restartHandler;
}
/** buildInitialPropertyMap create the initial set of properties from the contents of launch.ini
* and for a few other properties necessary to launch defaults are supplied if not provided.
* The value '@null' will set the map value to null.
* @return a map containing the initial properties
*/
@SuppressWarnings("rawtypes")
protected Map<String, String> buildInitialPropertyMap() {
Map<String, String> initialPropertyMap = new HashMap<>();
Properties launchProperties = loadProperties(resourceBase + LAUNCH_INI);
for (Iterator it = launchProperties.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
setInitialProperty(initialPropertyMap, key, value);
}
try {
// install.area if not specified
if (initialPropertyMap.get(OSGI_INSTALL_AREA) == null)
initialPropertyMap.put(OSGI_INSTALL_AREA, platformDirectory.toURL().toExternalForm());
// configuration.area if not specified
if (initialPropertyMap.get(OSGI_CONFIGURATION_AREA) == null) {
File configurationDirectory = new File(platformDirectory, "configuration"); //$NON-NLS-1$
if (!configurationDirectory.exists()) {
configurationDirectory.mkdirs();
}
initialPropertyMap.put(OSGI_CONFIGURATION_AREA, configurationDirectory.toURL().toExternalForm());
}
// instance.area if not specified
if (initialPropertyMap.get(OSGI_INSTANCE_AREA) == null) {
File workspaceDirectory = new File(platformDirectory, "workspace"); //$NON-NLS-1$
if (!workspaceDirectory.exists()) {
workspaceDirectory.mkdirs();
}
initialPropertyMap.put(OSGI_INSTANCE_AREA, workspaceDirectory.toURL().toExternalForm());
}
// read values from config.ini
Properties configurationProperties = loadConfigurationFile(initialPropertyMap);
// osgi.framework if not specified
if (initialPropertyMap.get(OSGI_FRAMEWORK) == null) {
String osgiFramework = configurationProperties.getProperty(OSGI_FRAMEWORK);
if (osgiFramework != null)
initialPropertyMap.put(OSGI_FRAMEWORK, osgiFramework);
}
// osgi.framework.extensions if not specified
if (initialPropertyMap.get(OSGI_FRAMEWORK_EXTENSIONS) == null) {
String osgiFrameworkExtensions = configurationProperties.getProperty(OSGI_FRAMEWORK_EXTENSIONS);
if (osgiFrameworkExtensions != null)
initialPropertyMap.put(OSGI_FRAMEWORK_EXTENSIONS, osgiFrameworkExtensions);
}
} catch (MalformedURLException e) {
throw new RuntimeException("Error establishing location"); //$NON-NLS-1$
}
return initialPropertyMap;
}
private void setInitialProperty(Map<String, String> initialPropertyMap, String key, String value) {
if (key.endsWith("*")) { //$NON-NLS-1$
if (value.equals(NULL_IDENTIFIER)) {
clearPrefixedSystemProperties(key.substring(0, key.length() - 1), initialPropertyMap);
}
} else if (value.equals(NULL_IDENTIFIER))
initialPropertyMap.put(key, null);
else
initialPropertyMap.put(key, value);
}
private Properties loadConfigurationFile(Map<String, String> initialPropertyMap) {
InputStream is = null;
try {
String installArea = initialPropertyMap.get(OSGI_INSTALL_AREA);
if (installArea.startsWith(FILE_SCHEME)) {
installArea = installArea.substring(FILE_SCHEME.length());
}
File installBase = new File(installArea);
String configurationArea = initialPropertyMap.get(OSGI_CONFIGURATION_AREA);
if (configurationArea.startsWith(FILE_SCHEME)) {
configurationArea = configurationArea.substring(FILE_SCHEME.length());
}
File configurationBase = new File(configurationArea);
if (!configurationBase.isAbsolute())
configurationBase = new File(installBase, configurationArea);
File configurationFile = new File(configurationBase, CONFIG_INI);
if (!configurationFile.exists())
return null;
Properties configProperties = new Properties();
is = new BufferedInputStream(new FileInputStream(configurationFile));
configProperties.load(is);
return configProperties;
} catch (Throwable t) {
context.log("Error reading configuration file -- " + t.toString()); //$NON-NLS-1$
return null;
} finally {
if (is != null)
try {
is.close();
} catch (IOException e) {
// unexpected
e.printStackTrace();
}
}
}
/**
* clearPrefixedSystemProperties clears System Properties by writing null properties in the targetPropertyMap that match a prefix
*/
private static void clearPrefixedSystemProperties(String prefix, Map<String, String> targetPropertyMap) {
for (@SuppressWarnings("rawtypes")
Iterator it = System.getProperties().keySet().iterator(); it.hasNext();) {
String propertyName = (String) it.next();
if (propertyName.startsWith(prefix) && !targetPropertyMap.containsKey(propertyName)) {
targetPropertyMap.put(propertyName, null);
}
}
}
/**
* buildCommandLineArguments parses the commandline config parameter into a set of arguments
* @return an array of String containing the commandline arguments
*/
protected String[] buildCommandLineArguments() {
List<String> args = new ArrayList<>();
String commandLine = config.getInitParameter(CONFIG_COMMANDLINE);
if (commandLine != null) {
StringTokenizer tokenizer = new StringTokenizer(commandLine, WS_DELIM);
while (tokenizer.hasMoreTokens()) {
String arg = tokenizer.nextToken();
if (arg.startsWith("\"")) { //$NON-NLS-1$
if (arg.endsWith("\"")) { //$NON-NLS-1$
if (arg.length() >= 2) {
// strip the beginning and ending quotes
arg = arg.substring(1, arg.length() - 1);
}
} else {
String remainingArg = tokenizer.nextToken("\""); //$NON-NLS-1$
arg = arg.substring(1) + remainingArg;
// skip to next whitespace separated token
tokenizer.nextToken(WS_DELIM);
}
} else if (arg.startsWith("'")) { //$NON-NLS-1$
if (arg.endsWith("'")) { //$NON-NLS-1$
if (arg.length() >= 2) {
// strip the beginning and ending quotes
arg = arg.substring(1, arg.length() - 1);
}
} else {
String remainingArg = tokenizer.nextToken("'"); //$NON-NLS-1$
arg = arg.substring(1) + remainingArg;
// skip to next whitespace separated token
tokenizer.nextToken(WS_DELIM);
}
} else if (arg.startsWith("-D")) { //$NON-NLS-1$
int matchIndex = arg.indexOf("=\""); //$NON-NLS-1$
if (matchIndex != -1) {
if (!arg.substring(matchIndex + 2).endsWith("\"") && tokenizer.hasMoreTokens()) { //$NON-NLS-1$
arg += tokenizer.nextToken("\"") + "\""; //$NON-NLS-1$ //$NON-NLS-2$
// skip to next whitespace separated token
tokenizer.nextToken(WS_DELIM);
}
}
}
args.add(arg);
}
}
return args.toArray(new String[] {});
}
/**
* stop is used to "shutdown" the framework and make it avialable for garbage collection.
* The default implementation also has special handling for Apache Commons Logging to "release" any
* resources associated with the frameworkContextClassLoader.
*/
public synchronized void stop() {
if (platformDirectory == null) {
context.log("Shutdown unnecessary. (not deployed)"); //$NON-NLS-1$
return;
}
if (frameworkClassLoader == null) {
context.log("Framework is already shutdown"); //$NON-NLS-1$
return;
}
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Class<?> clazz = frameworkClassLoader.loadClass(STARTER);
Method method = clazz.getDeclaredMethod("shutdown"); //$NON-NLS-1$
Thread.currentThread().setContextClassLoader(frameworkContextClassLoader);
method.invoke(clazz);
// ACL keys its loggers off of the ContextClassLoader which prevents GC without calling release.
// This section explicitly calls release if ACL is used.
try {
clazz = this.getClass().getClassLoader().loadClass("org.apache.commons.logging.LogFactory"); //$NON-NLS-1$
method = clazz.getDeclaredMethod("release", ClassLoader.class); //$NON-NLS-1$
method.invoke(clazz, frameworkContextClassLoader);
} catch (ClassNotFoundException e) {
// ignore, ACL is not being used
}
} catch (Exception e) {
context.log("Error while stopping Framework", e); //$NON-NLS-1$
return;
} finally {
frameworkClassLoader.close();
frameworkClassLoader = null;
frameworkContextClassLoader = null;
Thread.currentThread().setContextClassLoader(original);
}
}
/**
* copyResource is a convenience method to recursively copy resources from the ServletContext to
* an installation target. The default behavior will create a directory if the resourcepath ends
* in '/' and a file otherwise.
* @param resourcePath - The resource root path
* @param target - The root location where resources are to be copied
*/
protected void copyResource(String resourcePath, File target) {
if (resourcePath.endsWith("/")) { //$NON-NLS-1$
target.mkdir();
Set<String> paths = context.getResourcePaths(resourcePath);
if (paths == null)
return;
for (Iterator<String> it = paths.iterator(); it.hasNext();) {
String path = it.next();
File newFile = new File(target, path.substring(resourcePath.length()));
copyResource(path, newFile);
}
} else {
try {
if (target.createNewFile()) {
InputStream is = null;
OutputStream os = null;
try {
is = context.getResourceAsStream(resourcePath);
if (is == null)
return;
os = new FileOutputStream(target);
byte[] buffer = new byte[8192];
int bytesRead = is.read(buffer);
while (bytesRead != -1) {
os.write(buffer, 0, bytesRead);
bytesRead = is.read(buffer);
}
} finally {
if (is != null)
is.close();
if (os != null)
os.close();
}
}
} catch (IOException e) {
context.log("Error copying resources", e); //$NON-NLS-1$
}
}
}
/**
* deleteDirectory is a convenience method to recursively delete a directory
* @param directory - the directory to delete.
* @return was the delete successful
*/
protected static boolean deleteDirectory(File directory) {
if (directory.isDirectory()) {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
}
return directory.delete();
}
/**
* Used when to set the ContextClassLoader when the BridgeServlet delegates to a Servlet
* inside the framework
* @return a Classloader with the OSGi framework's context class loader.
*/
public synchronized ClassLoader getFrameworkContextClassLoader() {
return frameworkContextClassLoader;
}
/**
* Platfom Directory is where the OSGi software is installed
* @return the framework install location
*/
protected synchronized File getPlatformDirectory() {
return platformDirectory;
}
/**
* loadProperties is a convenience method to load properties from a servlet context resource
* @param resource - The target to read properties from
* @return the properties
*/
protected Properties loadProperties(String resource) {
Properties result = new Properties();
InputStream in = null;
try {
URL location = context.getResource(resource);
if (location != null) {
in = location.openStream();
result.load(in);
}
} catch (MalformedURLException e) {
// no url to load from
} catch (IOException e) {
// its ok if there is no file
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
return result;
}
/***************************************************************************
* See org.eclipse.core.launcher [copy of searchFor, findMax,
* compareVersion, getVersionElements] TODO: If these methods were made
* public and static we could use them directly
**************************************************************************/
/**
* Searches for the given target directory starting in the "plugins" subdirectory
* of the given location. If one is found then this location is returned;
* otherwise an exception is thrown.
* @param target
*
* @return the location where target directory was found
* @param start the location to begin searching
*/
protected String searchFor(final String target, String start) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File candidate) {
return candidate.getName().equals(target) || candidate.getName().startsWith(target + "_"); //$NON-NLS-1$
}
};
File[] candidates = new File(start).listFiles(filter);
if (candidates == null)
return null;
String[] arrays = new String[candidates.length];
for (int i = 0; i < arrays.length; i++) {
arrays[i] = candidates[i].getName();
}
int result = findMax(arrays);
if (result == -1)
return null;
return candidates[result].getAbsolutePath().replace(File.separatorChar, '/') + (candidates[result].isDirectory() ? "/" : ""); //$NON-NLS-1$//$NON-NLS-2$
}
protected int findMax(String[] candidates) {
int result = -1;
Object maxVersion = null;
for (int i = 0; i < candidates.length; i++) {
String name = candidates[i];
String version = ""; //$NON-NLS-1$ // Note: directory with version suffix is always > than directory without version suffix
int index = name.indexOf('_');
if (index != -1)
version = name.substring(index + 1);
Object currentVersion = getVersionElements(version);
if (maxVersion == null) {
result = i;
maxVersion = currentVersion;
} else {
if (compareVersion((Object[]) maxVersion, (Object[]) currentVersion) < 0) {
result = i;
maxVersion = currentVersion;
}
}
}
return result;
}
/**
* Compares version strings.
* @param left
* @param right
* @return result of comparison, as integer;
* <code><0</code> if left < right;
* <code>0</code> if left == right;
* <code>>0</code> if left > right;
*/
private int compareVersion(Object[] left, Object[] right) {
int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
if (result != 0)
return result;
result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
if (result != 0)
return result;
result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
if (result != 0)
return result;
return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
}
/**
* Do a quick parse of version identifier so its elements can be correctly compared.
* If we are unable to parse the full version, remaining elements are initialized
* with suitable defaults.
* @param version
* @return an array of size 4; first three elements are of type Integer (representing
* major, minor and service) and the fourth element is of type String (representing
* qualifier). Note, that returning anything else will cause exceptions in the caller.
*/
private Object[] getVersionElements(String version) {
if (version.endsWith(DOT_JAR))
version = version.substring(0, version.length() - 4);
Object[] result = {new Integer(0), new Integer(0), new Integer(0), ""}; //$NON-NLS-1$
StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
String token;
int i = 0;
while (t.hasMoreTokens() && i < 4) {
token = t.nextToken();
if (i < 3) {
// major, minor or service ... numeric values
try {
result[i++] = new Integer(token);
} catch (Exception e) {
// invalid number format - use default numbers (0) for the rest
break;
}
} else {
// qualifier ... string value
result[i++] = token;
}
}
return result;
}
/**
* The ChildFirstURLClassLoader alters regular ClassLoader delegation and will check the URLs
* used in its initialization for matching classes before delegating to it's parent.
* Sometimes also referred to as a ParentLastClassLoader
*/
protected static class ChildFirstURLClassLoader extends CloseableURLClassLoader {
private static final boolean CHILDFIRST_REGISTERED_AS_PARALLEL;
static {
boolean registeredAsParallel;
try {
Method parallelCapableMetod = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[]) null); //$NON-NLS-1$
parallelCapableMetod.setAccessible(true);
registeredAsParallel = ((Boolean) parallelCapableMetod.invoke(null, (Object[]) null)).booleanValue();
} catch (Throwable e) {
// must do everything to avoid failing in clinit
registeredAsParallel = false;
}
CHILDFIRST_REGISTERED_AS_PARALLEL = registeredAsParallel;
}
public ChildFirstURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, false);
}
@Override
public URL getResource(String name) {
URL resource = findResource(name);
if (resource == null) {
ClassLoader parent = getParent();
if (parent != null)
resource = parent.getResource(name);
}
return resource;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (isRegisteredAsParallel()) {
return loadClass0(name, resolve);
}
synchronized (this) {
return loadClass0(name, resolve);
}
}
private Class<?> loadClass0(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
clazz = findClass(name);
} catch (ClassNotFoundException e) {
ClassLoader parent = getParent();
if (parent != null)
clazz = parent.loadClass(name);
else
clazz = getSystemClassLoader().loadClass(name);
}
}
if (resolve)
resolveClass(clazz);
return clazz;
}
// we want to ensure that the framework has AllPermissions
@Override
protected PermissionCollection getPermissions(CodeSource codesource) {
return allPermissions;
}
@Override
protected boolean isRegisteredAsParallel() {
return CHILDFIRST_REGISTERED_AS_PARALLEL;
}
}
}