| /******************************************************************************* |
| * Copyright (c) 2005, 2018 Cognos Incorporated, IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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 = {Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(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++] = Integer.valueOf(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; |
| } |
| |
| } |
| |
| } |