// ======================================================================== // Copyright (c) 2010-2011 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.servletbridge; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Properties; import java.util.TreeMap; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import org.eclipse.equinox.servletbridge.FrameworkLauncher; /** * Extend the servletbridge FrameworkLauncher to support launching an equinox installation * made by p2director. */ public class FrameworkLauncherExtended extends FrameworkLauncher { /** * if the OSGI_INSTALL_AREA installed area is specified as a sytem property and matches a Folder on the file system, we don't copy the whole eclipse * installation instead we use that folder as it is */ private static final String DEPLOY_IN_PLACE_WHEN_INSTALL_AREA_IS_FOLDER = "org.eclipse.equinox.servletbridge.deployinplace"; //$NON-NLS-1$ public static final String FRAMEWORK_BOOTDELEGATION = "org.osgi.framework.bootdelegation"; private boolean deployedInPlace = false; private URL resourceBaseAsURL = null; protected static Boolean ASYNCH_START_IN_PROGRESS; protected static Throwable ASYNCH_START_FAILURE = null; /** * If the start is asynch we do it in a different thread and return immediately. */ @Override public synchronized void start() { if (ASYNCH_START_IN_PROGRESS == null && "true".equals(super.config.getInitParameter("asyncStart"))) { final ClassLoader webappCl = Thread.currentThread().getContextClassLoader(); Thread th = new Thread() { public void run() { Thread.currentThread().setContextClassLoader(webappCl); System.out.println("Jetty-Nested: Starting equinox asynchroneously."); FrameworkLauncherExtended.this.start(); System.out.println("Jetty-Nested: Finished starting equinox asynchroneously."); } }; ASYNCH_START_IN_PROGRESS = true; try { th.start(); } catch (Throwable t) { ASYNCH_START_FAILURE = t; if (t instanceof RuntimeException) { throw (RuntimeException)t; } else { throw new RuntimeException("Equinox failed to start", t); } } finally { ASYNCH_START_IN_PROGRESS = false; } } else { System.out.println("Jetty-Nested: Starting equinox synchroneously."); super.start(); System.out.println("Jetty-Nested: Finished starting equinox synchroneously."); } } /** * try to find the resource base for this webapp by looking for the launcher initialization file. */ protected void initResourceBase() { try { String resourceBaseStr = System.getProperty(OSGI_INSTALL_AREA); if (resourceBaseStr == null || resourceBaseStr.length() == 0) { resourceBaseStr = config.getInitParameter(OSGI_INSTALL_AREA); } if (resourceBaseStr != null && resourceBaseStr.length() != 0) { // If the path starts with a reference to a system property, resolve it. resourceBaseStr = resolveSystemProperty(resourceBaseStr); if (resourceBaseStr.startsWith("/WEB-INF/")) { String rpath = context.getRealPath(resourceBaseStr); if (rpath != null) { File rpathFile = new File(rpath); if (rpathFile.exists() && rpathFile.isDirectory() && rpathFile.canWrite()) { resourceBaseStr = rpath; } } } if (resourceBaseStr.startsWith("file://")) { resourceBaseAsURL = new URL(resourceBaseStr.replace(" ","%20")); //$NON-NLS-1$ //$NON-NLS-2$ } else if (new File(resourceBaseStr).exists()) { resourceBaseAsURL = new URL("file://" + new File(resourceBaseStr).getCanonicalPath().replace(" ","%20")); //$NON-NLS-1$ //$NON-NLS-2$ } else { resourceBaseAsURL = context.getResource(resourceBaseStr); } } else { if (context.getResource(RESOURCE_BASE + ECLIPSE) != null) { resourceBase = RESOURCE_BASE + ECLIPSE; } else { super.initResourceBase(); } resourceBaseAsURL = context.getResource(resourceBase); } } catch (MalformedURLException e) { // ignore } catch (IOException e) { // ignore } if (resourceBaseAsURL != null && resourceBaseAsURL.getProtocol().equals("file")) { //$NON-NLS-1$ File resBase = new File(resourceBaseAsURL.getPath()); if (resBase.exists() && resBase.isDirectory() && !Boolean.FALSE.toString().equalsIgnoreCase(System.getProperty(DEPLOY_IN_PLACE_WHEN_INSTALL_AREA_IS_FOLDER))) { __setPlatformDirectory(resBase); deployedInPlace = true; } } } /** * Override this method to be able to set default system properties computed on the fly depending on the environment where equinox and jetty-osgi are * deployed. * * @param resource * - The target to read properties from * @return the properties */ protected Properties loadProperties(String resource) { Properties props = super.loadProperties(resource); if (resource.equals(resourceBase + LAUNCH_INI) && deployedInPlace) { String osgiInstall = props.getProperty(OSGI_INSTALL_AREA); if (osgiInstall == null) { // compute the osgi install dynamically. props.put(OSGI_INSTALL_AREA,getPlatformDirectory().getAbsolutePath()); } String osgiFramework = props.getProperty(OSGI_FRAMEWORK); File pluginsFolder = null; if (osgiFramework == null && getPlatformDirectory() != null) { File osgiFrameworkF = findOsgiFramework(getPlatformDirectory()); pluginsFolder = osgiFrameworkF.getParentFile(); props.put(OSGI_FRAMEWORK,osgiFrameworkF.getAbsoluteFile().getAbsolutePath()); } String osgiFrameworkExtensions = props.getProperty(OSGI_FRAMEWORK_EXTENSIONS); if (osgiFrameworkExtensions == null) { //this bundle will make the javax.servlet and javax.servlet.http packages passed from //the bootstrap classloader into equinox osgiFrameworkExtensions = "org.eclipse.equinox.servletbridge.extensionbundle"; } File configIni = new File(getPlatformDirectory(), "configuration/config.ini"); Properties configIniProps = new Properties(); if (configIni.exists()) { System.out.println("Got the " + configIni.getAbsolutePath()); InputStream configIniStream = null; try { configIniStream = new FileInputStream(configIni); configIniProps.load(configIniStream); } catch (IOException ioe) { } finally { try { configIniStream.close(); } catch (IOException ioe2) {} } String confIniFrameworkExt = configIniProps.getProperty(OSGI_FRAMEWORK_EXTENSIONS); if (confIniFrameworkExt != null) { osgiFrameworkExtensions = osgiFrameworkExtensions + "," + confIniFrameworkExt; } } else { System.out.println("Unable to locate the " + configIni.getAbsolutePath()); } props.setProperty(OSGI_FRAMEWORK_EXTENSIONS,osgiFrameworkExtensions); //__deployExtensionBundle(pluginsFolder); deployExtensionBundle(pluginsFolder, true); String bootDeleg = props.getProperty(FRAMEWORK_BOOTDELEGATION); if (bootDeleg == null) { bootDeleg = configIniProps.getProperty(FRAMEWORK_BOOTDELEGATION); } if (bootDeleg == null || bootDeleg.indexOf("javax.servlet.http") == -1) { String add = "javax.servlet,javax.servlet.http,javax.servlet.resources"; if (bootDeleg != null) { bootDeleg += add; } else { bootDeleg = add; } props.setProperty(FRAMEWORK_BOOTDELEGATION, bootDeleg); } String jettyHome = System.getProperty("jetty.home"); if (jettyHome == null) { jettyHome = getPlatformDirectory().getAbsolutePath(); System.setProperty("jetty.home",jettyHome); props.setProperty("jetty.home",jettyHome); } else { jettyHome = resolveSystemProperty(jettyHome); } String etcJettyXml = System.getProperty("jetty.etc.config.urls"); if (etcJettyXml == null) { if (new File(jettyHome,"etc/jetty-osgi-nested.xml").exists()) { System.setProperty("jetty.etc.config.urls","etc/jetty-osgi-nested.xml"); props.setProperty("jetty.etc.config.urls","etc/jetty-osgi-nested.xml"); } } String startLevel = System.getProperty("osgi.startLevel"); if (startLevel == null) { startLevel = props.getProperty("osgi.startLevel"); if (startLevel == null) { startLevel = configIniProps.getProperty("osgi.startLevel"); } if (startLevel != null) { props.setProperty("osgi.startLevel",startLevel); System.setProperty("osgi.startLevel",startLevel); } } String logback = System.getProperty("logback.configurationFile"); if (logback == null) { File etcLogback = new File(jettyHome,"etc/logback-nested.xml"); if (!etcLogback.exists()) { etcLogback = new File(jettyHome,"etc/logback.xml"); } if (etcLogback.exists()) { System.setProperty("logback.configurationFile",etcLogback.getAbsolutePath()); props.setProperty("logback.configurationFile",etcLogback.getAbsolutePath()); } } else { logback = resolveSystemProperty(logback); } System.out.println("sysout: logback.configurationFile=" + System.getProperty("logback.configurationFile")); } return props; } /** * Look for the eclipse.ini file. or any *.ini Search for the argument -startup The next line is a relative path to the launcher osgi bundle: * ../bundlepool/plugins/org.eclipse.equinox.launcher_1.1.0.v20100507.jar Get that file, get the parent folder. This is where the plugins are located. In * that folder look for the * * @param installFolder * @return The osgi framework bundle. */ private File findOsgiFramework(File installFolder) { File[] fs = installFolder.listFiles(); for (int i = 0; i < fs.length; i++) { File f = fs[i]; if (f.isFile() && f.getName().endsWith(".ini") && !f.getName().equals(LAUNCH_INI)) { //$NON-NLS-1$ BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(f))); String line = null; String pathToLauncherJar = null; boolean gotStartArg = false; while ((line = br.readLine()) != null) { if (gotStartArg) { pathToLauncherJar = line.trim(); if (pathToLauncherJar.length() == 0) { continue; } break; } else if (line.trim().equals("-startup")) { //$NON-NLS-1$ gotStartArg = true; } } if (pathToLauncherJar != null) { File currFolder = getPlatformDirectory(); String oriStartup = pathToLauncherJar; while (pathToLauncherJar.startsWith("../")) { //$NON-NLS-1$ currFolder = currFolder.getParentFile(); pathToLauncherJar = pathToLauncherJar.substring(3); } File pluginsfolder = new File(currFolder,pathToLauncherJar).getParentFile(); // System.err.println("Got the pluginsfolder " + pluginsfolder); if (!pluginsfolder.exists()) { throw new IllegalStateException("The -startup argument in " + f.getPath() + //$NON-NLS-1$ " is " + oriStartup + ". It points to " + pluginsfolder.getPath() + //$NON-NLS-1$ //$NON-NLS-2$ " plugins directory that does not exists."); //$NON-NLS-1$ } TreeMap osgis = new TreeMap(); File[] plugins = pluginsfolder.listFiles(); for (int j = 0; j < plugins.length; j++) { File b = plugins[j]; if (b.isFile() && b.getName().startsWith(FRAMEWORK_BUNDLE_NAME + "_") && b.getName().endsWith(".jar")) { //$NON-NLS-1$ //$NON-NLS-2$ osgis.put(b.getName(),b); } } if (osgis.isEmpty()) { throw new IllegalStateException("The -startup argument in " + f.getPath() + //$NON-NLS-1$ " is " + oriStartup + //$NON-NLS-1$ ". It points to " + pluginsfolder.getPath() + //$NON-NLS-1$ " plugins directory but there is no org.eclipse.osgi.*.jar files there."); //$NON-NLS-1$ } File osgiFramework = (File)osgis.values().iterator().next(); String path = osgiFramework.getPath(); System.err.println("Using " + path + " for the osgi framework."); return osgiFramework; } } catch (IOException ioe) { // } finally { if (br != null) try { br.close(); } catch (IOException ii) { } } } } return null; } /** * recursively substitute the ${sysprop} by their actual system property. * ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no sysprop is defined. * Not the most efficient code but we are shooting for simplicity and speed of development here. * * @param value * @return */ public static String resolveSystemProperty(String value) { int ind = value.indexOf("${"); if (ind == -1) { return value; } int ind2 = value.indexOf('}', ind); if (ind2 == -1) { return value; } String sysprop = value.substring(ind+2, ind2); String defaultValue = null; int comma = sysprop.indexOf(','); if (comma != -1 && comma+1 != sysprop.length()) { defaultValue = sysprop.substring(comma+1); defaultValue = resolveSystemProperty(defaultValue); sysprop = sysprop.substring(0,comma); } else { defaultValue = "${" + sysprop + "}"; } String v = System.getProperty(sysprop); String reminder = value.length() > ind2 + 1 ? value.substring(ind2+1) : ""; reminder = resolveSystemProperty(reminder); if (v != null) { return value.substring(0, ind) + v + reminder; } else { return value.substring(0, ind) + defaultValue + reminder; } } // introspection trick to be able to set the private field platformDirectory private static Field _field; void __setPlatformDirectory(File platformDirectory) { try { if (_field == null) { _field = org.eclipse.equinox.servletbridge.FrameworkLauncher.class.getDeclaredField("platformDirectory"); //$NON-NLS-1$ _field.setAccessible(true); } _field.set(this,platformDirectory); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } //introspection trick to invoke the generateExtensionBundle method private static Method _deployExtensionBundleMethod; private void __deployExtensionBundle(File plugins) { //look for the extensionbundle //if it is already there no need to do something: for (String file : plugins.list()) { if (file.startsWith("org.eclipse.equinox.servletbridge.extensionbundle"))//EXTENSIONBUNDLE_DEFAULT_BSN { return; } } try { //invoke deployExtensionBundle(File plugins) if (_deployExtensionBundleMethod == null) { _deployExtensionBundleMethod = FrameworkLauncher.class.getDeclaredMethod("deployExtensionBundle", File.class); _deployExtensionBundleMethod.setAccessible(true); } _deployExtensionBundleMethod.invoke(this, plugins); } catch (Throwable t) { t.printStackTrace(); } } //--end of introspection to invoke deployExtensionBundle //from Framework with support for the equinox hook private static final String EXTENSIONBUNDLE_DEFAULT_BSN = "org.eclipse.equinox.servletbridge.extensionbundle"; //$NON-NLS-1$ private static final String EXTENSIONBUNDLE_DEFAULT_VERSION = "1.2.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_EXTENDED_FRAMEWORK_EXPORTS = "extendedFrameworkExports"; //$NON-NLS-1$ private void deployExtensionBundle(File plugins, boolean configureEquinoxHook) { // 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, configureEquinoxHook); 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, true); // } else { // processExtensionBundle(extensionBundleFile); } } private File findExtensionBundleFile(File plugins, final String extensionBundleBSN) { FileFilter extensionBundleFilter = new FileFilter() { 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(".jar")) { return fileName.substring(extensionBundleBSN.length() + 1, fileName.length() - ".jar".length()); } return fileName.substring(extensionBundleBSN.length() + 1); } private void generateExtensionBundle(File plugins, String extensionBundleBSN, String extensionBundleVersion, boolean configureEquinoxHook) { 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 servletVersion = context.getMajorVersion() + "." + context.getMinorVersion(); //$NON-NLS-1$ String 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 + ".jar"), mf, configureEquinoxHook); //$NON-NLS-1$ } private void writeJarFile(File jarFile, Manifest mf, boolean configureEquinoxHook) { try { JarOutputStream jos = null; try { jos = new JarOutputStream(new FileOutputStream(jarFile), mf); if (configureEquinoxHook) { //hook configurator properties: ZipEntry e = new ZipEntry("hookconfigurators.properties"); jos.putNextEntry(e); Properties props = new Properties(); props.put("hook.configurators", "org.eclipse.jetty.osgi.servletbridge.hook.ServletBridgeClassLoaderDelegateHook"); props.store(jos, ""); jos.closeEntry(); //the hook class e = new ZipEntry("org/eclipse/jetty/osgi/servletbridge/hook/ServletBridgeClassLoaderDelegateHook.class"); jos.putNextEntry(e); InputStream in = getClass().getResourceAsStream("/org/eclipse/jetty/osgi/servletbridge/hook/ServletBridgeClassLoaderDelegateHook.class"); byte[] buffer = new byte[512]; try { int n; while ((n = in.read(buffer)) != -1) { jos.write(buffer, 0, n); } } finally { in.close(); } jos.closeEntry(); } jos.finish(); } finally { if (jos != null) jos.close(); } } catch (IOException e) { context.log("Error writing extension bundle", e); //$NON-NLS-1$ } } //--from Framework with support for the equinox hook }