diff options
author | Jan Bartel | 2013-02-19 22:26:30 +0000 |
---|---|---|
committer | Jan Bartel | 2013-02-19 22:26:30 +0000 |
commit | 9af433473961dab9f5d534031fe0a6daa615a2a6 (patch) | |
tree | df568b77e825d0016af82c775bfed9cdc33b4aad /jetty-osgi/jetty-osgi-boot/src | |
parent | 39c5d6c141df91da0f480fbce2773d4f6749e347 (diff) | |
download | org.eclipse.jetty.project-9af433473961dab9f5d534031fe0a6daa615a2a6.tar.gz org.eclipse.jetty.project-9af433473961dab9f5d534031fe0a6daa615a2a6.tar.xz org.eclipse.jetty.project-9af433473961dab9f5d534031fe0a6daa615a2a6.zip |
committing to save changes but does not yet compile
Diffstat (limited to 'jetty-osgi/jetty-osgi-boot/src')
57 files changed, 8199 insertions, 692 deletions
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java new file mode 100644 index 0000000000..98ac22b6eb --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java @@ -0,0 +1,206 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.annotations; + +import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler; +import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; +import org.eclipse.jetty.annotations.ClassNameResolver; +import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; + +/** + * Extend the AnnotationConfiguration to support OSGi: + * Look for annotations inside WEB-INF/lib and also in the fragments and required bundles. + * Discover them using a scanner adapted to OSGi instead of the jarscanner. + */ +public class AnnotationConfiguration extends org.eclipse.jetty.annotations.AnnotationConfiguration +{ + + /** + * This parser scans the bundles using the OSGi APIs instead of assuming a jar. + */ + @Override + protected org.eclipse.jetty.annotations.AnnotationParser createAnnotationParser() + { + return new AnnotationParser(); + } + + /** + * Here is the order in which jars and osgi artifacts are scanned for discoverable annotations. + * <ol> + * <li>The container jars are scanned.</li> + * <li>The WEB-INF/classes are scanned</li> + * <li>The osgi fragment to the web bundle are parsed.</li> + * <li>The WEB-INF/lib are scanned</li> + * <li>The required bundles are parsed</li> + * </ol> + */ + @Override + public void parseWebInfLib (WebAppContext context, org.eclipse.jetty.annotations.AnnotationParser parser) + throws Exception + { + AnnotationParser oparser = (AnnotationParser)parser; + + Bundle webbundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE); + Bundle[] fragAndRequiredBundles = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(webbundle); + if (fragAndRequiredBundles != null) + { + //index: + for (Bundle bundle : fragAndRequiredBundles) + { + Resource bundleRes = oparser.indexBundle(bundle); + if (!context.getMetaData().getWebInfJars().contains(bundleRes)) + { + context.getMetaData().addWebInfJar(bundleRes); + } + } + + //scan the fragments + for (Bundle fragmentBundle : fragAndRequiredBundles) + { + if (fragmentBundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) + { + //a fragment indeed: + parseFragmentBundle(context,oparser,webbundle,fragmentBundle); + } + } + } + //scan ourselves + parseWebBundle(context,oparser,webbundle); + + //scan the WEB-INF/lib + super.parseWebInfLib(context,parser); + if (fragAndRequiredBundles != null) + { + //scan the required bundles + for (Bundle requiredBundle : fragAndRequiredBundles) + { + if (requiredBundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) + { + //a bundle indeed: + parseRequiredBundle(context,oparser,webbundle,requiredBundle); + } + } + } + } + + /** + * Scan a fragment bundle for servlet annotations + * @param context The webapp context + * @param parser The parser + * @param webbundle The current webbundle + * @param fragmentBundle The OSGi fragment bundle to scan + * @throws Exception + */ + protected void parseFragmentBundle(WebAppContext context, AnnotationParser parser, + Bundle webbundle, Bundle fragmentBundle) throws Exception + { + parseBundle(context,parser,webbundle,fragmentBundle); + } + + /** + * Scan a bundle required by the webbundle for servlet annotations + * @param context The webapp context + * @param parser The parser + * @param webbundle The current webbundle + * @param fragmentBundle The OSGi required bundle to scan + * @throws Exception + */ + protected void parseWebBundle(WebAppContext context, AnnotationParser parser, Bundle webbundle) + throws Exception + { + parseBundle(context,parser,webbundle,webbundle); + } + + /** + * Scan a bundle required by the webbundle for servlet annotations + * @param context The webapp context + * @param parser The parser + * @param webbundle The current webbundle + * @param fragmentBundle The OSGi required bundle to scan + * @throws Exception + */ + protected void parseRequiredBundle(WebAppContext context, AnnotationParser parser, + Bundle webbundle, Bundle requiredBundle) throws Exception + { + parseBundle(context,parser,webbundle,requiredBundle); + } + + protected void parseBundle(WebAppContext context, AnnotationParser parser, + Bundle webbundle, Bundle bundle) throws Exception + { + + Resource bundleRes = parser.getResource(bundle); + + parser.clearHandlers(); + for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers) + { + if (h instanceof AbstractDiscoverableAnnotationHandler) + { + if (webbundle == bundle) + ((AbstractDiscoverableAnnotationHandler)h).setResource(null); + else + ((AbstractDiscoverableAnnotationHandler)h).setResource(bundleRes); + } + } + parser.registerHandlers(_discoverableAnnotationHandlers); + parser.registerHandler(_classInheritanceHandler); + parser.registerHandlers(_containerInitializerAnnotationHandlers); + + parser.parse(bundle,createClassNameResolver(context)); + } + + /** + * Returns the same classname resolver than for the webInfjar scanner + * @param context + * @return + */ + protected ClassNameResolver createClassNameResolver(final WebAppContext context) + { + return createClassNameResolver(context,true,false,false,false); + } + + protected ClassNameResolver createClassNameResolver(final WebAppContext context, + final boolean excludeSysClass, final boolean excludeServerClass, final boolean excludeEverythingElse, + final boolean overrideIsParenLoaderIsPriority) + { + return new ClassNameResolver () + { + public boolean isExcluded (String name) + { + if (context.isSystemClass(name)) return excludeSysClass; + if (context.isServerClass(name)) return excludeServerClass; + return excludeEverythingElse; + } + + public boolean shouldOverride (String name) + { + //looking at system classpath + if (context.isParentLoaderPriority()) + return overrideIsParenLoaderIsPriority; + return !overrideIsParenLoaderIsPriority; + } + }; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationParser.java new file mode 100644 index 0000000000..096cb8a821 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/annotations/AnnotationParser.java @@ -0,0 +1,197 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.annotations; + +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; + +import org.eclipse.jetty.annotations.ClassNameResolver; +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.util.resource.Resource; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; + +/** + * + */ +public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationParser +{ + private Set<URI> _alreadyParsed = new HashSet<URI>(); + + private Map<URI,Bundle> _uriToBundle = new HashMap<URI, Bundle>(); + private Map<Bundle,Resource> _resourceToBundle = new HashMap<Bundle,Resource>(); + private Map<Bundle,URI> _bundleToUri = new HashMap<Bundle, URI>(); + + /** + * Keep track of a jetty URI Resource and its associated OSGi bundle. + * @param uri + * @param bundle + * @throws Exception + */ + protected Resource indexBundle(Bundle bundle) throws Exception + { + File bundleFile = BundleFileLocatorHelper.DEFAULT.getBundleInstallLocation(bundle); + Resource resource = Resource.newResource(bundleFile.toURI()); + URI uri = resource.getURI(); + _uriToBundle.put(uri,bundle); + _bundleToUri.put(bundle,uri); + _resourceToBundle.put(bundle,resource); + return resource; + } + protected URI getURI(Bundle bundle) + { + return _bundleToUri.get(bundle); + } + protected Resource getResource(Bundle bundle) + { + return _resourceToBundle.get(bundle); + } + /** + * + */ + @Override + public void parse (URI[] uris, ClassNameResolver resolver) + throws Exception + { + for (URI uri : uris) + { + Bundle associatedBundle = _uriToBundle.get(uri); + if (associatedBundle == null) + { + if (!_alreadyParsed.add(uri)) + { + continue; + } + //a jar in WEB-INF/lib or the WEB-INF/classes + //use the behavior of the super class for a standard jar. + super.parse(new URI[] {uri},resolver); + } + else + { + parse(associatedBundle,resolver); + } + } + } + + protected void parse(Bundle bundle, ClassNameResolver resolver) + throws Exception + { + URI uri = _bundleToUri.get(bundle); + if (!_alreadyParsed.add(uri)) + { + return; + } + + String bundleClasspath = (String)bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH); + if (bundleClasspath == null) + { + bundleClasspath = "."; + } + //order the paths first by the number of tokens in the path second alphabetically. + TreeSet<String> paths = new TreeSet<String>( + new Comparator<String>() + { + public int compare(String o1, String o2) + { + int paths1 = new StringTokenizer(o1,"/",false).countTokens(); + int paths2 = new StringTokenizer(o2,"/",false).countTokens(); + if (paths1 == paths2) + { + return o1.compareTo(o2); + } + return paths2 - paths1; + } + }); + boolean hasDotPath = false; + StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false); + while (tokenizer.hasMoreTokens()) + { + String token = tokenizer.nextToken().trim(); + if (!token.startsWith("/")) + { + token = "/" + token; + } + if (token.equals("/.")) + { + hasDotPath = true; + } + else if (!token.endsWith(".jar") && !token.endsWith("/")) + { + paths.add(token+"/"); + } + else + { + paths.add(token); + } + } + //support the development environment: maybe the classes are inside bin or target/classes + //this is certainly not useful in production. + //however it makes our life so much easier during development. + if (bundle.getEntry("/.classpath") != null) + { + if (bundle.getEntry("/bin/") != null) + { + paths.add("/bin/"); + } + else if (bundle.getEntry("/target/classes/") != null) + { + paths.add("/target/classes/"); + } + } + Enumeration classes = bundle.findEntries("/","*.class",true); + if (classes == null) + { + return; + } + while (classes.hasMoreElements()) + { + URL classUrl = (URL) classes.nextElement(); + String path = classUrl.getPath(); + //remove the longest path possible: + String name = null; + for (String prefixPath : paths) + { + if (path.startsWith(prefixPath)) + { + name = path.substring(prefixPath.length()); + break; + } + } + if (name == null && hasDotPath) + { + //remove the starting '/' + name = path.substring(1); + } + //transform into a classname to pass to the resolver + String shortName = name.replace('/', '.').substring(0,name.length()-6); + if ((resolver == null)|| (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName)))) + scanClass(classUrl.openStream()); + } + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java new file mode 100644 index 0000000000..777ebdf608 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java @@ -0,0 +1,291 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.JettyServerServiceTracker; +import org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper; +import org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.BundleTracker; + +/** + * Bootstrap jetty and publish a default Server instance as an OSGi service. + * + * Listen for other Server instances to be published as services and support them as deployment targets. + * + * Listen for Bundles to be activated, and deploy those that represent webapps to one of the known Server instances. + * + * <ol> + * <li>basic servlet [ok]</li> + * <li>basic jetty.xml [ok]</li> + * <li>basic jetty.xml and jetty-plus.xml [ok]</li> + * <li>basic jsp [ok]</li> + * <li>jsp with tag-libs [ok]</li> + * <li>test-jndi with atomikos and derby inside ${jetty.home}/lib/ext [ok]</li> + * </ul> + */ +public class JettyBootstrapActivator implements BundleActivator +{ + + private static JettyBootstrapActivator INSTANCE = null; + + public static JettyBootstrapActivator getInstance() + { + return INSTANCE; + } + + private ServiceRegistration _registeredServer; + + private Server _server; + + private JettyContextHandlerServiceTracker _jettyContextHandlerTracker; + + private PackageAdminServiceTracker _packageAdminServiceTracker; + + private BundleTracker _webBundleTracker; + + private BundleContext _bundleContext; + + private JettyServerServiceTracker _jettyServerServiceTracker; + + /** + * Setup a new jetty Server, registers it as a service. Setup the Service + * tracker for the jetty ContextHandlers that are in charge of deploying the + * webapps. Setup the BundleListener that supports the extender pattern for + * the jetty ContextHandler. + * + * @param context + */ + public void start(BundleContext context) throws Exception + { + INSTANCE = this; + _bundleContext = context; + + // track other bundles and fragments attached to this bundle that we + // should activate. + _packageAdminServiceTracker = new PackageAdminServiceTracker(context); + + // track Server instances that we should support as deployment targets + _jettyServerServiceTracker = new JettyServerServiceTracker(); + context.addServiceListener(_jettyServerServiceTracker, "(objectclass=" + Server.class.getName() + ")"); + + // track ContextHandler class instances and deploy them to one of the known Servers + _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(_jettyServerServiceTracker); + context.addServiceListener(_jettyContextHandlerTracker, "(objectclass=" + ContextHandler.class.getName() + ")"); + + // Create a default jetty instance right now. + DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context); + + // track Bundles and deploy those that represent webapps to one of the known Servers + _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, new WebBundleTrackerCustomizer()); + _webBundleTracker.open(); + } + + /** + * Stop the activator. + * + * @see + * org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception + { + try + { + + if (_webBundleTracker != null) + { + _webBundleTracker.close(); + _webBundleTracker = null; + } + if (_jettyContextHandlerTracker != null) + { + _jettyContextHandlerTracker.stop(); + context.removeServiceListener(_jettyContextHandlerTracker); + _jettyContextHandlerTracker = null; + } + if (_jettyServerServiceTracker != null) + { + _jettyServerServiceTracker.stop(); + context.removeServiceListener(_jettyServerServiceTracker); + _jettyServerServiceTracker = null; + } + if (_packageAdminServiceTracker != null) + { + _packageAdminServiceTracker.stop(); + context.removeServiceListener(_packageAdminServiceTracker); + _packageAdminServiceTracker = null; + } + if (_registeredServer != null) + { + try + { + _registeredServer.unregister(); + } + catch (IllegalArgumentException ill) + { + // already unregistered. + } + finally + { + _registeredServer = null; + } + } + } + finally + { + if (_server != null) + { + _server.stop(); + } + INSTANCE = null; + } + } + + /** + * Helper method that creates a new org.jetty.webapp.WebAppContext and + * registers it as an OSGi service. The tracker + * {@link JettyContextHandlerServiceTracker} will do the actual deployment. + * + * @param contributor The bundle + * @param webappFolderPath The path to the root of the webapp. Must be a + * path relative to bundle; either an absolute path. + * @param contextPath The context path. Must start with "/" + * @throws Exception + */ + public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath) throws Exception + { + checkBundleActivated(); + WebAppContext contextHandler = new WebAppContext(); + Dictionary<String,String> dic = new Hashtable<String,String>(); + dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath); + dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath); + String requireTldBundle = (String) contributor.getHeaders().get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + if (requireTldBundle != null) + { + dic.put(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE, requireTldBundle); + } + contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic); + } + + /** + * Helper method that creates a new org.jetty.webapp.WebAppContext and + * registers it as an OSGi service. The tracker + * {@link JettyContextHandlerServiceTracker} will do the actual deployment. + * + * @param contributor The bundle + * @param webappFolderPath The path to the root of the webapp. Must be a + * path relative to bundle; either an absolute path. + * @param contextPath The context path. Must start with "/" + * @param dic TODO: parameter description + * @throws Exception + */ + public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath, Dictionary<String, String> dic) throws Exception + { + checkBundleActivated(); + WebAppContext contextHandler = new WebAppContext(); + dic.put(OSGiWebappConstants.SERVICE_PROP_WAR, webappFolderPath); + dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH, contextPath); + contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic); + } + + /** + * Helper method that creates a new skeleton of a ContextHandler and + * registers it as an OSGi service. The tracker + * {@link JettyContextHandlerServiceTracker} will do the actual deployment. + * + * @param contributor The bundle that registers a new context + * @param contextFilePath The path to the file inside the bundle that + * defines the context. + * @throws Exception + */ + public static void registerContext(Bundle contributor, String contextFilePath) throws Exception + { + registerContext(contributor, contextFilePath, new Hashtable<String, String>()); + } + + /** + * Helper method that creates a new skeleton of a ContextHandler and + * registers it as an OSGi service. The tracker + * {@link JettyContextHandlerServiceTracker} will do the actual deployment. + * + * @param contributor The bundle that registers a new context + * @param contextFilePath The path to the file inside the bundle that + * defines the context. + * @param dic TODO: parameter description + * @throws Exception + */ + public static void registerContext(Bundle contributor, String contextFilePath, Dictionary<String, String> dic) throws Exception + { + checkBundleActivated(); + ContextHandler contextHandler = new ContextHandler(); + dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH, contextFilePath); + dic.put(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE, Boolean.TRUE.toString()); + contributor.getBundleContext().registerService(ContextHandler.class.getName(), contextHandler, dic); + } + + public static void unregister(String contextPath) + { + // todo + } + + /** + * Since org.eclipse.jetty.osgi.boot does not have a lazy activation policy + * when one of the static methods to register a webapp is called we should + * make sure that the bundle is started. + */ + private static void checkBundleActivated() + { + if (INSTANCE == null) + { + Bundle thisBundle = FrameworkUtil.getBundle(JettyBootstrapActivator.class); + try + { + thisBundle.start(); + } + catch (BundleException e) + { + // nevermind. + } + } + } + + /** + * @return The bundle context for this bundle. + */ + public static BundleContext getBundleContext() + { + checkBundleActivated(); + return INSTANCE._bundleContext; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiAppProvider.java index b23e3fa796..b23e3fa796 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiAppProvider.java diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiServerConstants.java new file mode 100644 index 0000000000..53b35c7fd6 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiServerConstants.java @@ -0,0 +1,55 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +/** + * Name of the properties that configure a jetty Server OSGi service. + */ +public class OSGiServerConstants +{ + //for managed jetty instances, name of the configuration parameters + /** + * PID of the jetty servers's ManagedFactory + */ + public static final String MANAGED_JETTY_SERVER_FACTORY_PID = "org.eclipse.jetty.osgi.boot.managedserverfactory"; + + /** + * The associated value of that configuration parameter is the name under which this + * instance of the jetty server is tracked. + * When a ContextHandler is deployed and it specifies the managedServerName property, it is deployed + * on the corresponding jetty managed server or it throws an exception: jetty server not available. + */ + public static final String MANAGED_JETTY_SERVER_NAME = "managedServerName"; + /** + * Name of the 'default' jetty server instance. + * Usually the first one to be created. + */ + public static final String MANAGED_JETTY_SERVER_DEFAULT_NAME = "defaultJettyServer"; + + /** + * List of URLs to the jetty.xml files that configure th server. + */ + public static final String MANAGED_JETTY_XML_CONFIG_URLS = "jetty.etc.config.urls"; + + /** + * List of URLs to the folders where the legacy J2EE shared libraries are stored aka lib/ext, lib/jsp etc. + */ + public static final String MANAGED_JETTY_SHARED_LIB_FOLDER_URLS = "managedJettySharedLibFolderUrls"; + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java new file mode 100644 index 0000000000..3cb21232ed --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +/** + * Name of the service properties for a ContextHandler that configure a webapp deployed on jetty OSGi. + */ +public class OSGiWebappConstants +{ + /** url scheme to deploy war file as bundled webapp */ + public static final String RFC66_WAR_URL_SCHEME = "war"; + + /** + * Name of the header that defines the context path for the embedded webapp. + */ + public static final String RFC66_WEB_CONTEXTPATH = "Web-ContextPath"; + + /** + * Name of the header that defines the path to the folder where the jsp + * files are extracted. + */ + public static final String RFC66_JSP_EXTRACT_LOCATION = "Jsp-ExtractLocation"; + + /** Name of the servlet context attribute that points to the bundle context. */ + public static final String RFC66_OSGI_BUNDLE_CONTEXT = "osgi-bundlecontext"; + + /** Name of the servlet context attribute that points to the bundle object. + * We can't always rely on the bundle-context as there might be no such thing. */ + public static final String JETTY_OSGI_BUNDLE = "osgi-bundle"; + + /** List of relative pathes within the bundle to the jetty context files. */ + public static final String JETTY_CONTEXT_FILE_PATH = "Jetty-ContextFilePath"; + + /** path within the bundle to the folder that contains the basic resources. */ + public static final String JETTY_WAR_FOLDER_PATH = "Jetty-WarFolderPath"; + + /** path within a fragment hosted by a web-bundle to a folder that contains basic resources. + * the path is appended to the lookup path where jetty locates static resources */ + public static final String JETTY_WAR_FRAGMENT_FOLDER_PATH = "Jetty-WarFragmentFolderPath"; + + /** path within a fragment hosted by a web-bundle to a folder that contains basic resources. + * The path is prefixed to the lookup path where jetty locates static resources: + * this will override static resources with the same name in the web-bundle. */ + public static final String JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH = "Jetty-WarPatchFragmentFolderPath"; + + // OSGi ContextHandler service properties. + /** web app context path */ + public static final String SERVICE_PROP_CONTEXT_PATH = "contextPath"; + + /** Path to the web application base folder */ + public static final String SERVICE_PROP_WAR = "war"; + + /** Extra classpath */ + public static final String SERVICE_PROP_EXTRA_CLASSPATH = "extraClasspath"; + + /** jetty context file path */ + public static final String SERVICE_PROP_CONTEXT_FILE_PATH = "contextFilePath"; + + /** web.xml file path */ + public static final String SERVICE_PROP_WEB_XML_PATH = "webXmlFilePath"; + + /** defaultweb.xml file path */ + public static final String SERVICE_PROP_DEFAULT_WEB_XML_PATH = "defaultWebXmlFilePath"; + + /** + * path to the base folder that overrides the computed bundle installation + * location if not null useful to install webapps or jetty context files + * that are in fact not embedded in a bundle + */ + public static final String SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE = "thisBundleInstall"; + + /** + * Comma separated list of bundles that contain tld file used by the webapp. + */ + public static final String REQUIRE_TLD_BUNDLE = "Require-TldBundle"; + /** + * Comma separated list of bundles that contain tld file used by the webapp. + * Both the name of the manifest header and the name of the service property. + */ + public static final String SERVICE_PROP_REQUIRE_TLD_BUNDLE = REQUIRE_TLD_BUNDLE; +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java new file mode 100644 index 0000000000..4b48aef2a3 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloader.java @@ -0,0 +1,65 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.jsp; + +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Tricky url classloader. In fact we don't want a real URLClassLoader: we want + * OSGi to provide its classloader and let it does. But to let + * {@link org.apache.jasper.compiler.TldLocationsCache} find the core tlds + * inside the jars we must be a URLClassLoader that returns an array of jars + * where tlds are stored when the method getURLs is called. + */ +public class TldLocatableURLClassloader extends URLClassLoader +{ + + private URL[] _jarsWithTldsInside; + + public TldLocatableURLClassloader(ClassLoader osgiClassLoader, URL[] jarsWithTldsInside) + { + super(new URL[] {},osgiClassLoader); + _jarsWithTldsInside = jarsWithTldsInside; + } + + /** + * @return the jars that contains tlds so that TldLocationsCache or + * TldScanner can find them. + */ + @Override + public URL[] getURLs() + { + return _jarsWithTldsInside; + } + + public String toString() + { + StringBuilder builder = new StringBuilder(); + + if (_jarsWithTldsInside != null) + { + for (URL u:_jarsWithTldsInside) + builder.append(" "+u.toString()); + return builder.toString(); + } + else + return super.toString(); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java new file mode 100644 index 0000000000..e4cf805aac --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java @@ -0,0 +1,69 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.jsp; + +import java.net.URL; + +/** + * Add a classloader to the + * org.apache.jasper.compiler.TldLocatableURLClassloader. Hopefuly not + * necessary: still experimenting. + * + * @see TldLocatableURLClassloader + */ +public class TldLocatableURLClassloaderWithInsertedJettyClassloader extends TldLocatableURLClassloader +{ + + private ClassLoader _internalClassLoader; + + /** + * + * @param osgiClassLoaderParent + * The parent classloader + * @param internalClassLoader + * The classloader that will be at the same level than the + * jarsWithTldsInside + * @param jarsWithTldsInside + * jars that are scanned for tld files. + */ + public TldLocatableURLClassloaderWithInsertedJettyClassloader(ClassLoader osgiClassLoaderParent, ClassLoader internalClassLoader, URL[] jarsWithTldsInside) + { + super(osgiClassLoaderParent,jarsWithTldsInside); + _internalClassLoader = internalClassLoader; + } + + protected Class<?> findClass(String name) throws ClassNotFoundException + { + try + { + return super.findClass(name); + } + catch (ClassNotFoundException cne) + { + if (_internalClassLoader != null) + { + return _internalClassLoader.loadClass(name); + } + else + { + throw cne; + } + } + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java new file mode 100644 index 0000000000..5624d07e62 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java @@ -0,0 +1,338 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.StringTokenizer; + +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * Called by the {@link JettyBootstrapActivator} during the starting of the + * bundle. If the system property 'jetty.home' is defined and points to a + * folder, then setup the corresponding jetty server. + */ +public class DefaultJettyAtJettyHomeHelper +{ + private static final Logger LOG = Log.getLogger(DefaultJettyAtJettyHomeHelper.class); + + /** + * contains a comma separated list of pathes to the etc/jetty-*.xml files + * used to configure jetty. By default the value is 'etc/jetty.xml' when the + * path is relative the file is resolved relatively to jettyhome. + */ + public static final String SYS_PROP_JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; + + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String SYS_PROP_JETTY_HOME = "jetty.home"; + + /** + * System property to point to a bundle that embeds a jetty configuration + * and that jetty configuration should be the default jetty server. First we + * look for jetty.home. If we don't find it then we look for this property. + */ + public static final String SYS_PROP_JETTY_HOME_BUNDLE = "jetty.home.bundle"; + + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String SYS_PROP_JETTY_HOST = "jetty.host"; + + /** + * Usual system property used as the port for http for a typical jetty + * configuration. + */ + public static final String SYS_PROP_JETTY_PORT = "jetty.port"; + + /** + * Usual system property used as the port for https for a typical jetty + * configuration. + */ + public static final String SYS_PROP_JETTY_PORT_SSL = "jetty.port.ssl"; + + /** + * Set of config files to apply to a jetty Server instance if none are supplied by SYS_PROP_JETTY_ETC_FILES + */ + public static final String DEFAULT_JETTY_ETC_FILES = "jetty.xml,jetty-selector.xml,jetty-deployer.xml"; + + /** + * Default location within bundle of a jetty home dir. + */ + public static final String DEFAULT_JETTYHOME = "/jettyhome"; + + /** + * Called by the JettyBootStrapActivator. If the system property jetty.home + * is defined and points to a folder, creates a corresponding jetty + * server. + * <p> + * If the system property jetty.home.bundle is defined and points to a + * bundle, look for the configuration of jetty inside that bundle. + * </p> + * <p> + * In both cases reads the system property 'jetty.etc.config.urls' to locate + * the configuration files for the deployed jetty. It is a comma separated + * list of URLs or relative paths inside the bundle or folder to the config + * files. If undefined it defaults to 'etc/jetty.xml'. In the case of the jetty.home.bundle, + * if no etc/jetty.xml file is found in the bundle, it will look for + * /jettyhome/etc/jetty-osgi-default.xml + * </p> + * <p> + * In both cases the system properties jetty.host, jetty.port and + * jetty.port.ssl are passed to the configuration files that might use them + * as part of their properties. + * </p> + */ + public static void startJettyAtJettyHome(BundleContext bundleContext) throws Exception + { + String jettyHomeSysProp = System.getProperty(SYS_PROP_JETTY_HOME); + String jettyHomeBundleSysProp = System.getProperty(SYS_PROP_JETTY_HOME_BUNDLE); + File jettyHome = null; + Bundle jettyHomeBundle = null; + if (jettyHomeSysProp != null) + { + jettyHomeSysProp = resolvePropertyValue(jettyHomeSysProp); + // bug 329621 + if (jettyHomeSysProp.startsWith("\"") && jettyHomeSysProp.endsWith("\"") || (jettyHomeSysProp.startsWith("'") && jettyHomeSysProp.endsWith("'"))) + { + jettyHomeSysProp = jettyHomeSysProp.substring(1, jettyHomeSysProp.length() - 1); + } + if (jettyHomeBundleSysProp != null) + { + LOG.warn("Both jetty.home and jetty.home.bundle property defined: jetty.home.bundle ignored."); + } + jettyHome = new File(jettyHomeSysProp); + if (!jettyHome.exists() || !jettyHome.isDirectory()) + { + LOG.warn("Unable to locate the jetty.home folder " + jettyHomeSysProp); + return; + } + } + else if (jettyHomeBundleSysProp != null) + { + jettyHomeBundleSysProp = resolvePropertyValue(jettyHomeBundleSysProp); + for (Bundle b : bundleContext.getBundles()) + { + if (b.getSymbolicName().equals(jettyHomeBundleSysProp)) + { + jettyHomeBundle = b; + break; + } + } + if (jettyHomeBundle == null) + { + LOG.warn("Unable to find the jetty.home.bundle named " + jettyHomeSysProp); + return; + } + + } + if (jettyHome == null && jettyHomeBundle == null) + { + LOG.warn("No default jetty created."); + return; + } + + Server server = new Server(); + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME); + + String configURLs = jettyHome != null ? getJettyConfigurationURLs(jettyHome) : getJettyConfigurationURLs(jettyHomeBundle); + properties.put(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS, configURLs); + + LOG.info("Configuring the default jetty server with " + configURLs); + + // these properties usually are the ones passed to this type of + // configuration. + setProperty(properties, SYS_PROP_JETTY_HOME, System.getProperty(SYS_PROP_JETTY_HOME)); + setProperty(properties, SYS_PROP_JETTY_HOST, System.getProperty(SYS_PROP_JETTY_HOST)); + setProperty(properties, SYS_PROP_JETTY_PORT, System.getProperty(SYS_PROP_JETTY_PORT)); + setProperty(properties, SYS_PROP_JETTY_PORT_SSL, System.getProperty(SYS_PROP_JETTY_PORT_SSL)); + + //register the Server instance as an OSGi service. + bundleContext.registerService(Server.class.getName(), server, properties); + // hookNestedConnectorToBridgeServlet(server); + + } + + /** + * Minimum setup for the location of the configuration files given a + * jettyhome folder. Reads the system property jetty.etc.config.urls and + * look for the corresponding jetty configuration files that will be used to + * setup the jetty server. + * + * @param jettyhome + * @return + */ + private static String getJettyConfigurationURLs(File jettyhome) + { + String jettyetc = System.getProperty(SYS_PROP_JETTY_ETC_FILES, "etc/jetty.xml"); + StringTokenizer tokenizer = new StringTokenizer(jettyetc, ";,", false); + StringBuilder res = new StringBuilder(); + while (tokenizer.hasMoreTokens()) + { + String next = tokenizer.nextToken().trim(); + if (!next.startsWith("/") && next.indexOf(':') == -1) + { + try + { + next = new File(jettyhome, next).toURI().toURL().toString(); + } + catch (MalformedURLException e) + { + LOG.warn(e); + continue; + } + } + appendToCommaSeparatedList(res, next); + } + return res.toString(); + } + + /** + * Minimum setup for the location of the configuration files given a + * configuration embedded inside a bundle. Reads the system property + * jetty.etc.config.urls and look for the corresponding jetty configuration + * files that will be used to setup the jetty server. + * + * @param jettyhome + * @return + */ + private static String getJettyConfigurationURLs(Bundle configurationBundle) + { + String files = System.getProperty(SYS_PROP_JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); + + StringTokenizer tokenizer = new StringTokenizer(files, ";,", false); + StringBuilder res = new StringBuilder(); + + while (tokenizer.hasMoreTokens()) + { + String etcFile = tokenizer.nextToken().trim(); + if (etcFile.startsWith("/") || etcFile.indexOf(":") != -1) + { + //file path is absolute + appendToCommaSeparatedList(res, etcFile); + } + else + { + //relative file path + Enumeration<URL> enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, etcFile); + + // default for org.eclipse.osgi.boot where we look inside + // jettyhome for the default embedded configuration. + // default inside jettyhome. this way fragments to the bundle + // can define their own configuration. + if ((enUrls == null || !enUrls.hasMoreElements())) + { + String tmp = DEFAULT_JETTYHOME+"/etc/"+etcFile; + enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, tmp); + LOG.info("Configuring jetty with the default embedded configuration:" + "bundle: " + + configurationBundle.getSymbolicName() + + " config: "+tmp); + } + if (enUrls == null || !enUrls.hasMoreElements()) + { + LOG.warn("Unable to locate a jetty configuration file for " + etcFile); + } + if (enUrls != null) + { + while (enUrls.hasMoreElements()) + { + appendToCommaSeparatedList(res, enUrls.nextElement().toString()); + } + } + } + } + return res.toString(); + } + + private static void appendToCommaSeparatedList(StringBuilder buffer, String value) + { + if (buffer.length() != 0) + { + buffer.append(","); + } + buffer.append(value); + } + + private static void setProperty(Dictionary<String,String> properties, String key, String value) + { + if (value != null) + { + properties.put(key, value); + } + } + + /** + * 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 resolvePropertyValue(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 = resolvePropertyValue(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 = resolvePropertyValue(reminder); + if (v != null) + { + return value.substring(0, ind) + v + reminder; + } + else + { + return value.substring(0, ind) + defaultValue + reminder; + } + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java new file mode 100644 index 0000000000..bb65462dff --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java @@ -0,0 +1,34 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +/** + * Keeps track of the running jetty servers. They are named. + */ +public interface IManagedJettyServerRegistry +{ + + /** + * @param managedServerName The server name + * @return the corresponding jetty server wrapped with its deployment + * properties. + */ + public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName); + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java new file mode 100644 index 0000000000..e172c75197 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java @@ -0,0 +1,172 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; + +/** + * Deploy the jetty server instances when they are registered as an OSGi + * service. + */ +public class JettyServerServiceTracker implements ServiceListener, IManagedJettyServerRegistry +{ + private static Logger LOG = Log.getLogger(JettyServerServiceTracker.class.getName()); + + /** + * Servers indexed by PIDs. PIDs are generated by the ConfigurationAdmin + * service. + */ + private Map<String, ServerInstanceWrapper> _serversIndexedByName = new HashMap<String, ServerInstanceWrapper>(); + + /** The context-handler to deactivate indexed by ServerInstanceWrapper */ + private Map<ServiceReference, ServerInstanceWrapper> _indexByServiceReference = new HashMap<ServiceReference, ServerInstanceWrapper>(); + + /** + * Stops each one of the registered servers. + */ + public void stop() + { + // not sure that this is really useful but here we go. + for (ServerInstanceWrapper wrapper : _serversIndexedByName.values()) + { + try + { + wrapper.stop(); + } + catch (Throwable t) + { + LOG.warn(t); + } + } + } + + /** + * Receives notification that a service has had a lifecycle change. + * + * @param ev The <code>ServiceEvent</code> object. + */ + public void serviceChanged(ServiceEvent ev) + { + ServiceReference sr = ev.getServiceReference(); + switch (ev.getType()) + { + case ServiceEvent.MODIFIED: + case ServiceEvent.UNREGISTERING: + { + ServerInstanceWrapper instance = unregisterInIndex(ev.getServiceReference()); + if (instance != null) + { + try + { + instance.stop(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + + if (ev.getType() == ServiceEvent.UNREGISTERING) + { + break; + } + else + { + // modified, meaning: we reload it. now that we stopped it; + // we can register it. + } + } + case ServiceEvent.REGISTERED: + { + try + { + Bundle contributor = sr.getBundle(); + Server server = (Server) contributor.getBundleContext().getService(sr); + ServerInstanceWrapper wrapper = registerInIndex(server, sr); + Properties props = new Properties(); + for (String key : sr.getPropertyKeys()) + { + Object value = sr.getProperty(key); + props.put(key, value); + } + wrapper.start(server, props); + } + catch (Exception e) + { + LOG.warn(e); + } + break; + } + } + } + + private ServerInstanceWrapper registerInIndex(Server server, ServiceReference sr) + { + String name = (String) sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + if (name == null) { throw new IllegalArgumentException("The property " + OSGiServerConstants.MANAGED_JETTY_SERVER_NAME + " is mandatory"); } + ServerInstanceWrapper wrapper = new ServerInstanceWrapper(name); + _indexByServiceReference.put(sr, wrapper); + _serversIndexedByName.put(name, wrapper); + return wrapper; + } + + /** + * Returns the ContextHandler to stop. + * + * @param reg + * @return the ContextHandler to stop. + */ + private ServerInstanceWrapper unregisterInIndex(ServiceReference sr) + { + ServerInstanceWrapper handler = _indexByServiceReference.remove(sr); + if (handler == null) + { + // a warning? + return null; + } + String name = handler.getManagedServerName(); + if (name != null) + { + _serversIndexedByName.remove(name); + } + return handler; + } + + /** + * @param managedServerName The server name + * @return the corresponding jetty server wrapped with its deployment + * properties. + */ + public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) + { + return _serversIndexedByName.get(managedServerName == null ? OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME : managedServerName); + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java new file mode 100644 index 0000000000..10f442f9c4 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -0,0 +1,444 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader; +import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.xml.sax.SAXParseException; + +/** + * ServerInstanceWrapper + * + * Configures and starts a jetty Server instance. + */ +public class ServerInstanceWrapper +{ + + /** + * The value of this property points to the parent director of the jetty.xml + * configuration file currently executed. Everything is passed as a URL to + * support the case where the bundle is zipped. + */ + public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url"; + + private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName()); + + private final String _managedServerName; + + /** + * The managed jetty server + */ + private Server _server; + + private ContextHandlerCollection _ctxtHandler; + + /** + * This is the class loader that should be the parent classloader of any + * webapp classloader. It is in fact the _libExtClassLoader with a trick to + * let the TldScanner find the jars where the tld files are. + */ + private ClassLoader _commonParentClassLoaderForWebapps; + + private DeploymentManager _deploymentManager; + + private OSGiAppProvider _provider; + + private WebBundleDeployerHelper _webBundleDeployerHelper; + + public ServerInstanceWrapper(String managedServerName) + { + _managedServerName = managedServerName; + } + + public String getManagedServerName() + { + return _managedServerName; + } + + /** + * The classloader that should be the parent classloader for each webapp + * deployed on this server. + * + * @return + */ + public ClassLoader getParentClassLoaderForWebapps() + { + return _commonParentClassLoaderForWebapps; + } + + /** + * @return The deployment manager registered on this server. + */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } + + /** + * @return The app provider registered on this server. + */ + public OSGiAppProvider getOSGiAppProvider() + { + return _provider; + } + + public Server getServer() + { + return _server; + } + + public WebBundleDeployerHelper getWebBundleDeployerHelp() + { + return _webBundleDeployerHelper; + } + + /** + * @return The collection of context handlers + */ + public ContextHandlerCollection getContextHandlerCollection() + { + return _ctxtHandler; + } + + public void start(Server server, Dictionary props) throws Exception + { + _server = server; + ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); + try + { + // passing this bundle's classloader as the context classloader + // makes sure there is access to all the jetty's bundles + ClassLoader libExtClassLoader = null; + String sharedURLs = (String) props.get(OSGiServerConstants.MANAGED_JETTY_SHARED_LIB_FOLDER_URLS); + + List<File> shared = sharedURLs != null ? extractFiles(sharedURLs) : null; + libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null, server, JettyBootstrapActivator.class.getClassLoader()); + + Thread.currentThread().setContextClassLoader(libExtClassLoader); + + configure(server, props); + + init(); + + // now that we have an app provider we can call the registration + // customizer. + + URL[] jarsWithTlds = getJarsWithTlds(); + _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds); + + server.start(); + _webBundleDeployerHelper = new WebBundleDeployerHelper(this); + } + catch (Exception e) + { + if (server != null) + { + try + { + server.stop(); + } + catch (Exception x) + { + LOG.ignore(x); + } + } + throw e; + } + finally + { + Thread.currentThread().setContextClassLoader(contextCl); + } + } + + + public void stop() + { + try + { + if (_server.isRunning()) + { + _server.stop(); + } + } + catch (Exception e) + { + LOG.warn(e); + } + } + + /** + * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. + * Should support a way to plug more bundles that contain taglibs. + * + * The jasper TldScanner expects a URLClassloader to parse a jar for the + * /META-INF/*.tld it may contain. We place the bundles that we know contain + * such tag-libraries. Please note that it will work if and only if the + * bundle is a jar (!) Currently we just hardcode the bundle that contains + * the jstl implementation. + * + * A workaround when the tld cannot be parsed with this method is to copy + * and paste it inside the WEB-INF of the webapplication where it is used. + * + * Support only 2 types of packaging for the bundle: - the bundle is a jar + * (recommended for runtime.) - the bundle is a folder and contain jars in + * the root and/or in the lib folder (nice for PDE development situations) + * Unsupported: the bundle is a jar that embeds more jars. + * + * @return + * @throws Exception + */ + private URL[] getJarsWithTlds() throws Exception + { + ArrayList<URL> res = new ArrayList<URL>(); + WebBundleDeployerHelper.staticInit();// that is not looking great. + for (WebappRegistrationCustomizer regCustomizer : WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS) + { + URL[] urls = regCustomizer.getJarsWithTlds(_provider, WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER); + for (URL url : urls) + { + if (!res.contains(url)) res.add(url); + } + } + if (!res.isEmpty()) + return res.toArray(new URL[res.size()]); + else + return null; + } + + private void configure(Server server, Dictionary props) throws Exception + { + String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS); + List<URL> jettyConfigurations = jettyConfigurationUrls != null ? extractResources(jettyConfigurationUrls) : null; + if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return; } + Map<String, Object> id_map = new HashMap<String, Object>(); + + //TODO need to put in the id of the server being configured + id_map.put("Server", server); + Map<String, String> properties = new HashMap<String, String>(); + Enumeration<Object> en = props.keys(); + while (en.hasMoreElements()) + { + Object key = en.nextElement(); + Object value = props.get(key); + properties.put(String.valueOf(key), String.valueOf(value)); + } + + for (URL jettyConfiguration : jettyConfigurations) + { + InputStream is = null; + try + { + // Execute a Jetty configuration file + Resource r = Resource.newResource(jettyConfiguration); + is = r.getInputStream(); + XmlConfiguration config = new XmlConfiguration(is); + config.getIdMap().putAll(id_map); + + // #334062 compute the URL of the folder that contains the + // jetty.xml conf file + // and set it as a property so we can compute relative paths + // from it. + String urlPath = jettyConfiguration.toString(); + int lastSlash = urlPath.lastIndexOf('/'); + if (lastSlash > 4) + { + urlPath = urlPath.substring(0, lastSlash); + Map<String, String> properties2 = new HashMap<String, String>(properties); + properties2.put(PROPERTY_THIS_JETTY_XML_FOLDER_URL, urlPath); + config.getProperties().putAll(properties2); + } + else + { + config.getProperties().putAll(properties); + } + config.configure(); + id_map = config.getIdMap(); + } + catch (SAXParseException saxparse) + { + LOG.warn("Unable to configure the jetty/etc file " + jettyConfiguration, saxparse); + throw saxparse; + } + finally + { + IO.close(is); + } + } + + } + + /** + * Must be called after the server is configured. + * + * It is assumed the server has already been configured with the ContextHandlerCollection structure. + * + * The server must have an instance of OSGiAppProvider. If one is not provided, it is created. + */ + private void init() + { + // Get the context handler + _ctxtHandler = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class); + + // get a deployerManager + Collection<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class); + if (deployers != null && !deployers.isEmpty()) + { + _deploymentManager = deployers.iterator().next(); + + for (AppProvider provider : _deploymentManager.getAppProviders()) + { + if (provider instanceof OSGiAppProvider) + { + _provider = (OSGiAppProvider) provider; + break; + } + } + if (_provider == null) + { + // create it on the fly with reasonable default values. + try + { + _provider = new OSGiAppProvider(); + _provider.setMonitoredDirResource(Resource.newResource(getDefaultOSGiContextsHome(new File(System.getProperty("jetty.home"))).toURI())); + } + catch (IOException e) + { + LOG.warn(e); + } + _deploymentManager.addAppProvider(_provider); + } + } + + if (_ctxtHandler == null || _provider == null) throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured"); + + } + + /** + * @return The default folder in which the context files of the osgi bundles + * are located and watched. Or null when the system property + * "jetty.osgi.contexts.home" is not defined. If the configuration + * file defines the OSGiAppProvider's context. This will not be + * taken into account. + */ + File getDefaultOSGiContextsHome(File jettyHome) + { + String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home"); + if (jettyContextsHome != null) + { + File contextsHome = new File(jettyContextsHome); + if (!contextsHome.exists() || !contextsHome.isDirectory()) + { + throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" + + jettyContextsHome + + " must exist and be a folder"); + } + return contextsHome; + } + return new File(jettyHome, "/contexts"); + } + + File getOSGiContextsHome() + { + return _provider.getContextXmlDirAsFile(); + } + + /** + * @return the urls in this string. + */ + private List<URL> extractResources(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List<URL> urls = new ArrayList<URL>(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok))); + } + catch (Throwable mfe) + { + LOG.warn(mfe); + } + } + return urls; + } + + /** + * Get the folders that might contain jars for the legacy J2EE shared + * libraries + */ + private List<File> extractFiles(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List<File> files = new ArrayList<File>(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + URL url = new URL(tok); + url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url); + if (url.getProtocol().equals("file")) + { + Resource res = Resource.newResource(url); + File folder = res.getFile(); + if (folder != null) + { + files.add(folder); + } + } + } + catch (Throwable mfe) + { + LOG.warn(mfe); + } + } + return files; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java new file mode 100644 index 0000000000..502d972783 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java @@ -0,0 +1,87 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; + +/** + * Internal interface for the class that deploys a webapp on a server. Used as + * we migrate from the single instance of the jety server to multiple jetty + * servers. + */ +public interface IWebBundleDeployerHelper +{ + + /** + * when this property is present, the type of context handler registered is + * not known in advance. + */ + public static final String INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE = "unknownContextHandlerType"; + + /** + * Deploy a new web application on the jetty server. + * + * @param bundle The bundle + * @param webappFolderPath The path to the root of the webapp. Must be a + * path relative to bundle; either an absolute path. + * @param contextPath The context path. Must start with "/" + * @param extraClasspath + * @param overrideBundleInstallLocation + * @param requireTldBundle The list of bundles's symbolic names that contain + * tld files that are required by this WAB. + * @param webXmlPath + * @param defaultWebXmlPath TODO: parameter description + * @return The contexthandler created and started + * @throws Exception + */ + public abstract WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath, + String overrideBundleInstallLocation, String requireTldBundle, String webXmlPath, + String defaultWebXmlPath, WebAppContext webAppContext) throws Exception; + + /** + * Stop a ContextHandler and remove it from the collection. + * + * @see ContextDeployer#undeploy + * @param contextHandler + * @throws Exception + */ + public abstract void unregister(ContextHandler contextHandler) throws Exception; + + /** + * This type of registration relies on jetty's complete context xml file. + * Context encompasses jndi and all other things. This makes the definition + * of the webapp a lot more self-contained. + * + * @param contributor + * @param contextFileRelativePath + * @param extraClasspath + * @param overrideBundleInstallLocation + * @param requireTldBundle The list of bundles'symbolic name that contain + * tld files for this webapp. + * @param handler the context handler passed in the server reference that + * will be configured, deployed and started. + * @return The contexthandler created and started + * @throws Exception + */ + public abstract ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, + String overrideBundleInstallLocation, String requireTldBundle, ContextHandler handler) throws Exception; + +}
\ No newline at end of file diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java new file mode 100644 index 0000000000..00dea2dfb7 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java @@ -0,0 +1,390 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Scanner; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; + +/** + * When a {@link ContextHandler} service is activated we look into it and if the + * corresponding webapp is actually not configured then we go and register it. + * <p> + * The idea is to always go through this class when we deploy a new webapp on + * jetty. + * </p> + * <p> + * We are exposing each web-application as an OSGi service. This lets us update + * the webapps and stop/start them directly at the OSGi layer. It also give us + * many ways to declare those services: Declarative Services for example. <br/> + * It is a bit different from the way the HttpService works where we would have + * a WebappService and we woud register a webapp onto it. <br/> + * It does not go against RFC-66 nor does it prevent us from supporting the + * WebappContainer semantics. + * </p> + */ +public class JettyContextHandlerServiceTracker implements ServiceListener +{ + private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName()); + + /** New style: ability to manage multiple jetty instances */ + private final IManagedJettyServerRegistry _registry; + + /** The context-handler to deactivate indexed by context handler */ + private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>(); + + /** + * The index is the bundle-symbolic-name/path/to/context/file when there is + * such thing + */ + private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>(); + + /** in charge of detecting changes in the osgi contexts home folder. */ + private Scanner _scanner; + + /** + * @param registry + */ + public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception + { + _registry = registry; + } + + public void stop() throws Exception + { + if (_scanner != null) + { + _scanner.stop(); + } + // the class that created the server is also in charge of stopping it. + // nothing to stop in the WebappRegistrationHelper + + } + + /** + * @param contextHome Parent folder where the context files can override the + * context files defined in the web bundles: equivalent to the + * contexts folder in a traditional jetty installation. when + * null, just do nothing. + */ + protected void setupContextHomeScanner(File contextHome) throws IOException + { + if (contextHome == null) { return; } + final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath(); + _scanner = new Scanner(); + _scanner.setRecursive(true); + _scanner.setReportExistingFilesOnStartup(false); + _scanner.addListener(new Scanner.DiscreteListener() + { + public void fileAdded(String filename) throws Exception + { + // adding a file does not create a new app, + // it just reloads it with the new custom file. + // well, if the file does not define a context handler, + // then in fact it does remove it. + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + + public void fileChanged(String filename) throws Exception + { + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + + public void fileRemoved(String filename) throws Exception + { + // removing a file does not remove the app: + // it just goes back to the default embedded in the bundle. + // well, if there was no default then it does remove it. + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + }); + + } + + /** + * Receives notification that a service has had a lifecycle change. + * + * @param ev The <code>ServiceEvent</code> object. + */ + public void serviceChanged(ServiceEvent ev) + { + ServiceReference sr = ev.getServiceReference(); + switch (ev.getType()) + { + case ServiceEvent.MODIFIED: + case ServiceEvent.UNREGISTERING: + { + ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference()); + if (ctxtHandler != null && !ctxtHandler.isStopped()) + { + try + { + getWebBundleDeployerHelp(sr).unregister(ctxtHandler); + } + catch (Exception e) + { + __logger.warn(e); + } + } + } + if (ev.getType() == ServiceEvent.UNREGISTERING) + { + break; + } + else + { + // modified, meaning: we reload it. now that we stopped it; + // we can register it. + } + case ServiceEvent.REGISTERED: + { + Bundle contributor = sr.getBundle(); + BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext(); + ContextHandler contextHandler = (ContextHandler) context.getService(sr); + if (contextHandler.getServer() != null) + { + // is configured elsewhere. + return; + } + String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + if (contextHandler instanceof WebAppContext && contextFilePath == null) + // it could be a web-application that will in fact be configured + // via a context file. + // that case is identified by the fact that the contextFilePath + // is not null + // in that case we must use the register context methods. + { + WebAppContext webapp = (WebAppContext) contextHandler; + String contextPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); + if (contextPath == null) + { + contextPath = webapp.getContextPath(); + } + String webXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH); + if (webXmlPath == null) + { + webXmlPath = webapp.getDescriptor(); + } + String defaultWebXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH); + if (defaultWebXmlPath == null) + { + String jettyHome = System.getProperty(DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME); + if (jettyHome != null) + { + File etc = new File(jettyHome, "etc"); + if (etc.exists() && etc.isDirectory()) + { + File webDefault = new File(etc, "webdefault.xml"); + if (webDefault.exists()) + defaultWebXmlPath = webDefault.getAbsolutePath(); + else + defaultWebXmlPath = webapp.getDefaultsDescriptor(); + } + else + defaultWebXmlPath = webapp.getDefaultsDescriptor(); + } + } + String war = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR); + try + { + IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); + if (deployerHelper == null) + { + + } + else + { + WebAppContext handler = deployerHelper.registerWebapplication(contributor, + war, + contextPath, + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), + webXmlPath, defaultWebXmlPath, webapp); + if (handler != null) + { + registerInIndex(handler, sr); + } + } + } + catch (Throwable e) + { + __logger.warn(e); + } + } + else + { + // consider this just an empty skeleton: + if (contextFilePath == null) { throw new IllegalArgumentException("the property contextFilePath is required"); } + try + { + IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); + if (deployerHelper == null) + { + // more warnings? + } + else + { + if (Boolean.TRUE.toString().equals(sr.getProperty(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE))) + { + contextHandler = null; + } + ContextHandler handler = deployerHelper.registerContext(contributor, + contextFilePath, + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), + (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), + contextHandler); + if (handler != null) + { + registerInIndex(handler, sr); + } + } + } + catch (Throwable e) + { + __logger.warn(e); + } + } + } + break; + } + } + + private void registerInIndex(ContextHandler handler, ServiceReference sr) + { + _indexByServiceReference.put(sr, handler); + String key = getSymbolicNameAndContextFileKey(sr); + if (key != null) + { + _indexByContextFile.put(key, sr); + } + } + + /** + * Returns the ContextHandler to stop. + * + * @param reg + * @return the ContextHandler to stop. + */ + private ContextHandler unregisterInIndex(ServiceReference sr) + { + ContextHandler handler = _indexByServiceReference.remove(sr); + String key = getSymbolicNameAndContextFileKey(sr); + if (key != null) + { + _indexByContextFile.remove(key); + } + if (handler == null) + { + // a warning? + return null; + } + return handler; + } + + /** + * @param sr + * @return The key for a context file within the osgi contexts home folder. + */ + private String getSymbolicNameAndContextFileKey(ServiceReference sr) + { + String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + if (contextFilePath != null) { return sr.getBundle().getSymbolicName() + "/" + contextFilePath; } + return null; + } + + /** + * Called by the scanner when one of the context files is changed. + * + * @param contextFileFully + */ + public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath) + { + String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath); + if (key == null) { return; } + ServiceReference sr = _indexByContextFile.get(key); + if (sr == null) + { + // nothing to do? + return; + } + serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr)); + } + + /** + * @param canFilename + * @return + */ + private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath) + { + if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath)) + { + // why are we here: this does not look like a child of the osgi + // contexts home. + // warning? + return null; + } + return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\', '/'); + } + + /** + * @return The server on which this webapp is meant to be deployed + */ + private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) + { + if (_registry == null) { return null; } + if (managedServerName == null) + { + managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; + } + ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName); + // System.err.println("Returning " + managedServerName + " = " + + // wrapper); + return wrapper; + } + + private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr) + { + if (_registry == null) { return null; } + String managedServerName = (String) sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName); + return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java new file mode 100644 index 0000000000..c66fa26b12 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java @@ -0,0 +1,213 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.server.Server; + +/** + * Helper to create a URL class-loader with the jars inside + * ${jetty.home}/lib/ext and ${jetty.home}/resources. In an ideal world, every + * library is an OSGi bundle that does loads nicely. To support standard jars or + * bundles that cannot be loaded in the current OSGi environment, we support + * inserting the jars in the usual jetty/lib/ext folders in the proper classpath + * for the webapps. + * <p> + * Also the folder resources typically contains central configuration files for + * things like: log config and others. We enable fragments to register classes + * that are called back and passed those resources to do what they need to do. + * </p> + * <p> + * For example the test-jndi webapplication depends on derby, derbytools, + * atomikos none of them are osgi bundles. we can either re-package them or we + * can place them in the usual lib/ext. <br/> + * In fact jasper's jsp libraries should maybe place in lib/ext too. + * </p> + * <p> + * The drawback is that those libraries will not be available in the OSGi + * classloader. Note that we could have setup those jars as embedded jars of the + * current bundle. However, we would need to know in advance what are those jars + * which was not acceptable. Also having those jars in a URLClassLoader seem to + * be required for some cases. For example jaspers' TldLocationsCache (replaced + * by TldScanner for servlet-3.0). <br/> + * Also all the dependencies of those libraries must be resolvable directly from + * the JettyBootstrapActivator bundle as it is set as the parent classloader. For + * example: if atomikos is placed in lib/ext it will work if and only if + * JettyBootstrapActivator import the necessary packages from javax.naming*, + * javax.transaction*, javax.mail* etc Most of the common cases of javax are + * added as optional import packages into jetty bootstrapper plugin. When there + * are not covered: please make a request or create a fragment or register a + * bundle with a buddy-policy onto the jetty bootstrapper.. + * </p> + * <p> + * Alternatives to placing jars in lib/ext + * <ol> + * <li>Bundle the jars in an osgi bundle. Have the webapp(s) that context + * depends on them depend on that bundle. Things will go well for jetty.</li> + * <li>Bundle those jars in an osgi bundle-fragment that targets the + * jetty-bootstrap bundle</li> + * <li>Use equinox Buddy-Policy: register a buddy of the jetty bootstrapper + * bundle. (least favorite: it will work only on equinox)</li> + * </ol> + * </p> + */ +public class LibExtClassLoaderHelper +{ + + /** + * Class called back + */ + public interface IFilesInJettyHomeResourcesProcessor + { + void processFilesInResourcesFolder(File jettyHome, Map<String, File> filesInResourcesFolder); + } + + public static Set<IFilesInJettyHomeResourcesProcessor> registeredFilesInJettyHomeResourcesProcessors = new HashSet<IFilesInJettyHomeResourcesProcessor>(); + + /** + * @param server + * @return a url classloader with the jars of resources, lib/ext and the + * jars passed in the other argument. The parent classloader usually + * is the JettyBootStrapper (an osgi classloader. + * @throws MalformedURLException + */ + public static ClassLoader createLibEtcClassLoader(File jettyHome, Server server, ClassLoader parentClassLoader) throws MalformedURLException + { + if (jettyHome == null) { return parentClassLoader; } + ArrayList<URL> urls = new ArrayList<URL>(); + File jettyResources = new File(jettyHome, "resources"); + if (jettyResources.exists()) + { + // make sure it contains something else than README: + Map<String, File> jettyResFiles = new HashMap<String, File>(); + for (File f : jettyResources.listFiles()) + { + jettyResFiles.put(f.getName(), f); + if (f.getName().toLowerCase(Locale.ENGLISH).startsWith("readme")) + { + continue; + } + else + { + if (urls.isEmpty()) + { + urls.add(jettyResources.toURI().toURL()); + } + } + } + processFilesInResourcesFolder(jettyHome, jettyResFiles); + } + File libExt = new File(jettyHome, "lib/ext"); + if (libExt.exists()) + { + for (File f : libExt.listFiles()) + { + if (f.getName().endsWith(".jar")) + { + // cheap to tolerate folders so let's do it. + URL url = f.toURI().toURL(); + if (f.isFile()) + {// is this necessary anyways? + url = new URL("jar:" + url.toString() + "!/"); + } + urls.add(url); + } + } + } + + return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader); + } + + /** + * @param server + * @return a url classloader with the jars of resources, lib/ext and the + * jars passed in the other argument. The parent classloader usually + * is the JettyBootStrapper (an osgi classloader). If there was no + * extra jars to insert, then just return the parentClassLoader. + * @throws MalformedURLException + */ + public static ClassLoader createLibExtClassLoader(List<File> jarsContainerOrJars, List<URL> otherJarsOrFolder, Server server, ClassLoader parentClassLoader) + throws MalformedURLException + { + if (jarsContainerOrJars == null && otherJarsOrFolder == null) { return parentClassLoader; } + List<URL> urls = new ArrayList<URL>(); + if (otherJarsOrFolder != null) + { + urls.addAll(otherJarsOrFolder); + } + if (jarsContainerOrJars != null) + { + for (File libExt : jarsContainerOrJars) + { + if (libExt.isDirectory()) + { + for (File f : libExt.listFiles()) + { + if (f.getName().endsWith(".jar")) + { + // cheap to tolerate folders so let's do it. + URL url = f.toURI().toURL(); + if (f.isFile()) + { + // is this necessary anyways? + url = new URL("jar:" + url.toString() + "!/"); + } + urls.add(url); + } + } + } + } + } + return new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassLoader); + } + + /** + * When we find files typically used for central logging configuration we do + * what it takes in this method to do what the user expects. Without + * depending too much directly on a particular logging framework. + * <p> + * We can afford to do some implementation specific code for a logging + * framework only in a fragment. <br/> + * Trying to configure log4j and logback in here. + * </p> + * <p> + * We recommend that slf4j jars are all placed in the osgi framework. And a + * single implementation if possible packaged as an osgi bundle is there. + * </p> + */ + protected static void processFilesInResourcesFolder(File jettyHome, Map<String, File> childrenFiles) + { + for (IFilesInJettyHomeResourcesProcessor processor : registeredFilesInJettyHomeResourcesProcessors) + { + processor.processFilesInResourcesFolder(jettyHome, childrenFiles); + } + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java new file mode 100644 index 0000000000..77b07a719d --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java @@ -0,0 +1,285 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.jar.JarFile; + +import javax.servlet.http.HttpServlet; + +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppClassLoader; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleReference; + +/** + * Extends the webappclassloader to insert the classloader provided by the osgi + * bundle at the same level than any other jars palced in the webappclassloader. + */ +public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference +{ + + private Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName().toString()); + + /** + * when a logging framework is setup in the osgi classloaders, it can access + * this and register the classes that must not be found in the jar. + */ + public static Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>(); + + public static void addClassThatIdentifiesAJarThatMustBeRejected(Class<?> zclass) + { + JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclass.getName().replace('.', '/') + ".class"); + } + + public static void addClassThatIdentifiesAJarThatMustBeRejected(String zclassName) + { + JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclassName.replace('.', '/') + ".class"); + } + + static + { + addClassThatIdentifiesAJarThatMustBeRejected(HttpServlet.class); + } + + private ClassLoader _osgiBundleClassLoader; + + private Bundle _contributor; + + private boolean _lookInOsgiFirst = true; + + private Set<String> _libsAlreadyInManifest = new HashSet<String>(); + + /** + * @param parent The parent classloader. In this case + * @param context The WebAppContext + * @param contributor The bundle that defines this web-application. + * @throws IOException + */ + public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor, BundleClassLoaderHelper bundleClassLoaderHelper) + throws IOException + { + super(parent, context); + _contributor = contributor; + _osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor); + } + + /** + * Returns the <code>Bundle</code> that defined this web-application. + * + * @return The <code>Bundle</code> object associated with this + * <code>BundleReference</code>. + */ + public Bundle getBundle() + { + return _contributor; + } + + /** + * Reads the manifest. If the manifest is already configured to loads a few + * libs we should not add them to the classpath of the webapp. Not really + * important as we resolve classes through the osgi classloader first and + * then default on the libs of the webapp. + */ + private void computeLibsAlreadyInOSGiClassLoader() + { + // TODO + } + + @Override + public Enumeration<URL> getResources(String name) throws IOException + { + Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name); + Enumeration<URL> urls = super.getResources(name); + if (_lookInOsgiFirst) + { + return Collections.enumeration(toList(osgiUrls, urls)); + } + else + { + return Collections.enumeration(toList(urls, osgiUrls)); + } + } + + @Override + public URL getResource(String name) + { + if (_lookInOsgiFirst) + { + URL url = _osgiBundleClassLoader.getResource(name); + return url != null ? url : super.getResource(name); + } + else + { + URL url = super.getResource(name); + return url != null ? url : _osgiBundleClassLoader.getResource(name); + } + } + + private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2) + { + List<URL> list = new ArrayList<URL>(); + while (e != null && e.hasMoreElements()) + list.add(e.nextElement()); + while (e2 != null && e2.hasMoreElements()) + list.add(e2.nextElement()); + return list; + } + + /** + * + */ + protected Class<?> findClass(String name) throws ClassNotFoundException + { + try + { + return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name); + } + catch (ClassNotFoundException cne) + { + try + { + return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name); + } + catch (ClassNotFoundException cne2) + { + throw cne; + } + } + } + + /** + * Parse the classpath ourselves to be able to filter things. This is a + * derivative work of the super class + */ + @Override + public void addClassPath(String classPath) throws IOException + { + + StringTokenizer tokenizer = new StringTokenizer(classPath, ",;"); + while (tokenizer.hasMoreTokens()) + { + String path = tokenizer.nextToken(); + Resource resource = getContext().newResource(path); + + // Resolve file path if possible + File file = resource.getFile(); + if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED)) + { + super.addClassPath(path); + } + else + { + __logger.info("Did not add " + path + " to the classloader of the webapp " + getContext()); + } + } + + } + + /** + * @param lib + * @return true if the lib should be included in the webapp classloader. + */ + private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles) + { + try + { + if (file.isDirectory()) + { + for (String criteria : pathToClassFiles) + { + if (new File(file, criteria).exists()) { return false; } + } + } + else + { + JarFile jar = null; + try + { + jar = new JarFile(file); + for (String criteria : pathToClassFiles) + { + if (jar.getEntry(criteria) != null) { return false; } + } + } + finally + { + if (jar != null) try + { + jar.close(); + } + catch (IOException ioe) + { + } + } + } + } + catch (IOException e) + { + // nevermind. just trying our best + __logger.ignore(e); + } + return true; + } + + private static Field _contextField; + + /** + * In the case of the generation of a webapp via a jetty context file we + * need a proper classloader to setup the app before we have the + * WebappContext So we place a fake one there to start with. We replace it + * with the actual webapp context with this method. We also apply the + * extraclasspath there at the same time. + */ + public void setWebappContext(WebAppContext webappContext) + { + try + { + if (_contextField == null) + { + _contextField = WebAppClassLoader.class.getDeclaredField("_context"); + _contextField.setAccessible(true); + } + _contextField.set(this, webappContext); + if (webappContext.getExtraClasspath() != null) + { + addClassPath(webappContext.getExtraClasspath()); + } + } + catch (Throwable t) + { + // humf that will hurt if it does not work. + __logger.warn("Unable to set webappcontext", t); + } + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java index 7ed6b7e81e..7ed6b7e81e 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java new file mode 100644 index 0000000000..db976358dd --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java @@ -0,0 +1,271 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + + +import java.net.URL; +import java.util.Dictionary; + +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +/** + * Support bundles that declare the webapp directly through headers in their + * manifest. + * <p> + * Those headers will define a new WebApplication: + * <ul> + * <li>Web-ContextPath</li> + * <li>Jetty-WarFolderPath</li> + * </ul> + * </p> + * <p> + * Those headers will define a new app started via a jetty-context or a list of + * them. ',' column is the separator between the various context files. + * <ul> + * <li>Jetty-ContextFilePath</li> + * </ul> + * </p> + * And generate a jetty WebAppContext or another ContextHandler then registers + * it as service. Kind of simpler than declarative services and their xml files. + * Also avoid having the contributing bundle depend on jetty's package for + * WebApp. + * + * @author hmalphettes + */ +public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer +{ + private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class); + + /** + * A bundle is being added to the <code>BundleTracker</code>. + * + * <p> + * This method is called before a bundle which matched the search parameters + * of the <code>BundleTracker</code> is added to the + * <code>BundleTracker</code>. This method should return the object to be + * tracked for the specified <code>Bundle</code>. The returned object is + * stored in the <code>BundleTracker</code> and is available from the + * {@link BundleTracker#getObject(Bundle) getObject} method. + * + * @param bundle The <code>Bundle</code> being added to the + * <code>BundleTracker</code>. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event + * associated with the call to this method. + * @return The object to be tracked for the specified <code>Bundle</code> + * object or <code>null</code> if the specified <code>Bundle</code> + * object should not be tracked. + */ + public Object addingBundle(Bundle bundle, BundleEvent event) + { + if (bundle.getState() == Bundle.ACTIVE) + { + boolean isWebBundle = register(bundle); + return isWebBundle ? bundle : null; + } + else if (bundle.getState() == Bundle.STOPPING) + { + unregister(bundle); + } + else + { + // we should not be called in that state as + // we are registered only for ACTIVE and STOPPING + } + return null; + } + + /** + * A bundle tracked by the <code>BundleTracker</code> has been modified. + * + * <p> + * This method is called when a bundle being tracked by the + * <code>BundleTracker</code> has had its state modified. + * + * @param bundle The <code>Bundle</code> whose state has been modified. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event + * associated with the call to this method. + * @param object The tracked object for the specified bundle. + */ + public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) + { + // nothing the web-bundle was already track. something changed. + // we only reload the webapps if the bundle is stopped and restarted. + if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE) + { + unregister(bundle); + } + if (bundle.getState() == Bundle.ACTIVE) + { + register(bundle); + } + } + + /** + * A bundle tracked by the <code>BundleTracker</code> has been removed. + * + * <p> + * This method is called after a bundle is no longer being tracked by the + * <code>BundleTracker</code>. + * + * @param bundle The <code>Bundle</code> that has been removed. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event + * associated with the call to this method. + * @param object The tracked object for the specified bundle. + */ + public void removedBundle(Bundle bundle, BundleEvent event, Object object) + { + unregister(bundle); + } + + /** + * @param bundle + * @return true if this bundle in indeed a web-bundle. + */ + private boolean register(Bundle bundle) + { + Dictionary<?, ?> dic = bundle.getHeaders(); + String warFolderRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); + if (warFolderRelativePath != null) + { + String contextPath = getWebContextPath(bundle, dic, false); + if (contextPath == null || !contextPath.startsWith("/")) + { + LOG.warn("The manifest header '" + OSGiWebappConstants.JETTY_WAR_FOLDER_PATH + + ": " + + warFolderRelativePath + + "' in the bundle " + + bundle.getSymbolicName() + + " is not valid: there is no Web-ContextPath defined in the manifest."); + return false; + } + // create the corresponding service and publish it in the context of + // the contributor bundle. + try + { + JettyBootstrapActivator.registerWebapplication(bundle, warFolderRelativePath, contextPath); + return true; + } + catch (Throwable e) + { + LOG.warn("Starting the web-bundle " + bundle.getSymbolicName() + " threw an exception.", e); + return true;// maybe it did not work maybe it did. safer to track this bundle. + } + } + else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null) + { + String contextFileRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); + if (contextFileRelativePath == null) + { + // nothing to register here. + return false; + } + // support for multiple webapps in the same bundle: + String[] pathes = contextFileRelativePath.split(",;"); + for (String path : pathes) + { + try + { + JettyBootstrapActivator.registerContext(bundle, path.trim()); + } + catch (Throwable e) + { + LOG.warn(e); + } + } + return true; + } + else + { + // support for OSGi-RFC66; disclaimer, no access to the actual + // (draft) of the spec: just a couple of posts on the + // world-wide-web. + URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml"); + if (rfc66Webxml == null && dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) == null) + { + return false;// no webapp in here + } + // this is risky: should we make sure that there is no classes and + // jars directly available + // at the root of the of the bundle: otherwise they are accessible + // through the browser. we should enforce that the whole classpath + // is + // pointing to files and folders inside WEB-INF. We should + // filter-out + // META-INF too + String rfc66ContextPath = getWebContextPath(bundle, dic, rfc66Webxml == null); + try + { + JettyBootstrapActivator.registerWebapplication(bundle, ".", rfc66ContextPath); + return true; + } + catch (Throwable e) + { + LOG.warn(e); + return true;// maybe it did not work maybe it did. safer to track this bundle. + } + } + } + + private String getWebContextPath(Bundle bundle, Dictionary<?, ?> dic, boolean webinfWebxmlExists) + { + String rfc66ContextPath = (String) dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (rfc66ContextPath == null) + { + if (!webinfWebxmlExists) { return null; } + // extract from the last token of the bundle's location: + // (really ? + // could consider processing the symbolic name as an alternative + // the location will often reflect the version. + // maybe this is relevant when the file is a war) + String location = bundle.getLocation(); + String toks[] = location.replace('\\', '/').split("/"); + rfc66ContextPath = toks[toks.length - 1]; + // remove .jar, .war etc: + int lastDot = rfc66ContextPath.lastIndexOf('.'); + if (lastDot != -1) + { + rfc66ContextPath = rfc66ContextPath.substring(0, lastDot); + } + } + if (!rfc66ContextPath.startsWith("/")) + { + rfc66ContextPath = "/" + rfc66ContextPath; + } + return rfc66ContextPath; + } + + private void unregister(Bundle bundle) + { + // nothing to do: when the bundle is stopped, each one of its service + // reference is also stopped and that is what we use to stop the + // corresponding + // webapps registered in that bundle. + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java new file mode 100644 index 0000000000..4c4382f3d4 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelper.java @@ -0,0 +1,54 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper; +import org.osgi.framework.Bundle; + +/** + * Is there a clean OSGi way to go from the Bundle object to the classloader of + * the Bundle ? You can certainly take a class inside the bundle and get the + * bundle's classloader that way. Getting the classloader directly from the + * bundle would be nice. + * <p> + * We could use fragments that are specific to each OSGi implementation. Using + * introspection here to keep packaging simple and avoid the multiplication of + * the jars. + * </p> + * <p> + * The default implementation relies on introspection and supports equinox-3.5 + * and felix-2.0.0 + * </p> + */ +public interface BundleClassLoaderHelper +{ + + /** The name of the custom implementation for this interface in a fragment. */ + public static final String CLASS_NAME = "org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperImpl"; + + /** The default instance supports felix and equinox */ + public static BundleClassLoaderHelper DEFAULT = new DefaultBundleClassLoaderHelper(); + + /** + * @return The classloader of a given bundle. Assuming the bundle is + * started. + */ + public ClassLoader getBundleClassLoader(Bundle bundle); + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java new file mode 100644 index 0000000000..ebba3dbd6e --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java @@ -0,0 +1,93 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.io.File; +import java.net.URL; +import java.util.Enumeration; + +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; +import org.osgi.framework.Bundle; + +/** + * From a bundle to its location on the filesystem. Assumes the bundle is not a + * jar. + * + * @author hmalphettes + */ +public interface BundleFileLocatorHelper +{ + + /** The name of the custom implementation for this interface in a fragment. */ + public static final String CLASS_NAME = "org.eclipse.jetty.osgi.boot.utils.FileLocatorHelperImpl"; + + /** The default instance supports felix and equinox */ + public static BundleFileLocatorHelper DEFAULT = new DefaultFileLocatorHelper(); + + /** + * Works with equinox, felix, nuxeo and probably more. Not exactly in the + * spirit of OSGi but quite necessary to support self-contained webapps and + * other situations. + * <p> + * Currently only works with bundles that are not jar. + * </p> + * + * @param bundle The bundle + * @return Its installation location as a file. + * @throws Exception + */ + public File getBundleInstallLocation(Bundle bundle) throws Exception; + + /** + * Locate a file inside a bundle. + * + * @param bundle + * @param path + * @return file object + * @throws Exception + */ + public File getFileInBundle(Bundle bundle, String path) throws Exception; + + /** + * If the bundle is a jar, returns the jar. If the bundle is a folder, look + * inside it and search for jars that it returns. + * <p> + * Good enough for our purpose (TldLocationsCache when it scans for tld + * files inside jars alone. In fact we only support the second situation for + * development purpose where the bundle was imported in pde and the classes + * kept in a jar. + * </p> + * + * @param bundle + * @return The jar(s) file that is either the bundle itself, either the jars + * embedded inside it. + */ + public File[] locateJarsInsideBundle(Bundle bundle) throws Exception; + + /** + * Helper method equivalent to Bundle#getEntry(String entryPath) except that + * it searches for entries in the fragments by using the findEntries method. + * + * @param bundle + * @param entryPath + * @return null or all the entries found for that path. + */ + public Enumeration<URL> findEntries(Bundle bundle, String entryPath); + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java new file mode 100644 index 0000000000..951443f062 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java @@ -0,0 +1,60 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.net.URL; + +import org.eclipse.jetty.osgi.boot.OSGiAppProvider; + +/** + * Fix various shortcomings with the way jasper parses the tld files. + */ +public interface WebappRegistrationCustomizer +{ + /** + * we could do something a lot more pluggable with a custom header in the + * manifest or some customer declarative services let's keep it simple for + * now. hopefully the rest of the world won't need to customize this. + */ + public static final String CLASS_NAME = "org.eclipse.jetty.osgi.boot.jasper.WebappRegistrationCustomizerImpl"; + + /** + * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. + * Should support a way to plug more bundles that contain taglibs. + * + * The jasper TldScanner expects a URLClassloader to parse a jar for the + * /META-INF/*.tld it may contain. We place the bundles that we know contain + * such tag-libraries. Please note that it will work if and only if the + * bundle is a jar (!) Currently we just hardcode the bundle that contains + * the jstl implemenation. + * + * A workaround when the tld cannot be parsed with this method is to copy + * and paste it inside the WEB-INF of the webapplication where it is used. + * + * Support only 2 types of packaging for the bundle: - the bundle is a jar + * (recommended for runtime.) - the bundle is a folder and contain jars in + * the root and/or in the lib folder (nice for PDE developement situations) + * Unsupported: the bundle is a jar that embeds more jars. + * + * @return array of URLs + * @throws Exception + */ + URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper fileLocator) throws Exception; + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java new file mode 100644 index 0000000000..b1cfcc4897 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java @@ -0,0 +1,242 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.osgi.framework.Bundle; + +/** + * Default implementation of the BundleClassLoaderHelper. Uses introspection to + * support equinox-3.5 and felix-2.0.0 + */ +public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper +{ + + private static boolean identifiedOsgiImpl = false; + + private static Class BundleWiringClass = null; + private static Method BundleWiringClass_getClassLoader_method = null; + private static Method BundleClass_adapt_method = null; + + private static boolean isEquinox = false; + + private static boolean isFelix = false; + + private static void init(Bundle bundle) + { + identifiedOsgiImpl = true; + + try + { + BundleWiringClass = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring"); + if (BundleWiringClass != null) + { + BundleWiringClass_getClassLoader_method = BundleWiringClass.getDeclaredMethod("getClassLoader", new Class[] {}); + BundleClass_adapt_method = bundle.getClass().getDeclaredMethod("adapt", new Class[] { Class.class }); + BundleClass_adapt_method.setAccessible(true); + return; + } + } + catch (Throwable t) + { + //nevermind: an older version of OSGi where BundleWiring is not availble + //t.printStackTrace(); + } + + if (!bundle.getClass().getName().startsWith("org.apache.felix")) + { + try + { + isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null; + } + catch (Throwable t) + { + isEquinox = false; + } + } + if (!isEquinox) + { + try + { + isFelix = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl") != null; + } + catch (Throwable t2) + { + isFelix = false; + } + } + } + + /** + * Assuming the bundle is started. + * + * @param bundle + * @return classloader object + */ + public ClassLoader getBundleClassLoader(Bundle bundle) + { + //Older OSGi implementations: + String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator"); + if (bundleActivator == null) + { + bundleActivator = (String) bundle.getHeaders().get("Jetty-ClassInBundle"); + } + if (bundleActivator != null) + { + try + { + return bundle.loadClass(bundleActivator).getClassLoader(); + } + catch (ClassNotFoundException e) + { + // should not happen as we are called if the bundle is started + // anyways. + e.printStackTrace(); + } + } + // resort to introspection + if (!identifiedOsgiImpl) + { + init(bundle); + } + //This works for OSGi 4.2 and more recent. Aka version 1.6 + //It is using ava reflection to execute: + //(BundleClassLoader) bundle.adapt(BundleWiring.class).getClassLoader() + if (BundleClass_adapt_method != null && BundleWiringClass_getClassLoader_method != null) + { + try + { + Object bundleWiring = BundleClass_adapt_method.invoke(bundle, BundleWiringClass); + return (ClassLoader)BundleWiringClass_getClassLoader_method.invoke(bundleWiring, new Object[] {}); + } + catch (Throwable t) + { + t.printStackTrace(); + return null; + } + } + if (isEquinox) + { + return internalGetEquinoxBundleClassLoader(bundle); + } + else if (isFelix) { return internalGetFelixBundleClassLoader(bundle); } + return null; + } + + private static Method Equinox_BundleHost_getBundleLoader_method; + + private static Method Equinox_BundleLoader_createClassLoader_method; + + private static ClassLoader internalGetEquinoxBundleClassLoader(Bundle bundle) + { + // assume equinox: + try + { + if (Equinox_BundleHost_getBundleLoader_method == null) + { + Equinox_BundleHost_getBundleLoader_method = + bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost").getDeclaredMethod("getBundleLoader", new Class[] {}); + Equinox_BundleHost_getBundleLoader_method.setAccessible(true); + } + Object bundleLoader = Equinox_BundleHost_getBundleLoader_method.invoke(bundle, new Object[] {}); + if (Equinox_BundleLoader_createClassLoader_method == null && bundleLoader != null) + { + Equinox_BundleLoader_createClassLoader_method = + bundleLoader.getClass().getClassLoader().loadClass("org.eclipse.osgi.internal.loader.BundleLoader").getDeclaredMethod("createClassLoader", new Class[] {}); + Equinox_BundleLoader_createClassLoader_method.setAccessible(true); + } + return (ClassLoader) Equinox_BundleLoader_createClassLoader_method.invoke(bundleLoader, new Object[] {}); + } + catch (Throwable t) + { + t.printStackTrace(); + } + return null; + } + + private static Field Felix_BundleImpl_m_modules_field; + + private static Field Felix_ModuleImpl_m_classLoader_field; + + private static Field Felix_BundleImpl_m_revisions_field; + + + private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle) + { + // assume felix: + try + { + // now get the current module from the bundle. + // and return the private field m_classLoader of ModuleImpl + if (Felix_BundleImpl_m_modules_field == null) + { + Felix_BundleImpl_m_modules_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.BundleImpl").getDeclaredField("m_modules"); + Felix_BundleImpl_m_modules_field.setAccessible(true); + } + + // Figure out which version of the modules is exported + Object currentModuleImpl; + try + { + Object[] moduleArray = (Object[]) Felix_BundleImpl_m_modules_field.get(bundle); + currentModuleImpl = moduleArray[moduleArray.length - 1]; + } + catch (Throwable t2) + { + @SuppressWarnings("unchecked") + List<Object> moduleArray = (List<Object>) Felix_BundleImpl_m_modules_field.get(bundle); + currentModuleImpl = moduleArray.get(moduleArray.size() - 1); + } + + if (Felix_ModuleImpl_m_classLoader_field == null && currentModuleImpl != null) + { + Felix_ModuleImpl_m_classLoader_field = bundle.getClass().getClassLoader().loadClass("org.apache.felix.framework.ModuleImpl").getDeclaredField("m_classLoader"); + Felix_ModuleImpl_m_classLoader_field.setAccessible(true); + } + // first make sure that the classloader is ready: + // the m_classLoader field must be initialized by the + // ModuleImpl.getClassLoader() private method. + ClassLoader cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); + if (cl == null) + { + // looks like it was not ready: + // the m_classLoader field must be initialized by the + // ModuleImpl.getClassLoader() private method. + // this call will do that. + bundle.loadClass("java.lang.Object"); + cl = (ClassLoader) Felix_ModuleImpl_m_classLoader_field.get(currentModuleImpl); + return cl; + } + else + { + return cl; + } + } + catch (Throwable t) + { + t.printStackTrace(); + } + return null; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java new file mode 100644 index 0000000000..3028aa6c2c --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java @@ -0,0 +1,446 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils.internal; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.zip.ZipFile; + +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.resource.FileResource; +import org.eclipse.jetty.util.resource.Resource; +import org.osgi.framework.Bundle; + +/** + * From a bundle to its location on the filesystem. + * Often assumes the bundle is not a jar. + * + * @author hmalphettes + */ +public class DefaultFileLocatorHelper implements BundleFileLocatorHelper +{ + + // hack to locate the file-system directly from the bundle. + // support equinox, felix and nuxeo's osgi implementations. + // not tested on nuxeo and felix just yet. + // The url nuxeo and felix return is created directly from the File so it + // should work. + private static Field BUNDLE_ENTRY_FIELD = null; + + private static Field FILE_FIELD = null; + + private static Field BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = null;// ZipBundleFile + + // inside + // DirZipBundleEntry + + private static Field ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = null;// ZipFile + + /** + * Works with equinox, felix, nuxeo and probably more. Not exactly in the + * spirit of OSGi but quite necessary to support self-contained webapps and + * other situations. + * + * @param bundle The bundle + * @return Its installation location as a file. + * @throws Exception + */ + public File getBundleInstallLocation(Bundle bundle) throws Exception + { + // String installedBundles = System.getProperty("osgi.bundles"); + // grab the MANIFEST.MF's url + // and then do what it takes. + URL url = bundle.getEntry("/META-INF/MANIFEST.MF"); + + if (url.getProtocol().equals("file")) + { + // some osgi frameworks do use the file protocole directly in some + // situations. Do use the FileResource to transform the URL into a + // File: URL#toURI is broken + return new FileResource(url).getFile().getParentFile().getParentFile(); + } + else if (url.getProtocol().equals("bundleentry")) + { + // say hello to equinox who has its own protocol. + // we use introspection like there is no tomorrow to get access to + // the File + + URLConnection con = url.openConnection(); + con.setUseCaches(Resource.getDefaultUseCaches()); // work around + // problems where + // url connections + // cache + // references to + // jars + + if (BUNDLE_ENTRY_FIELD == null) + { + BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry"); + BUNDLE_ENTRY_FIELD.setAccessible(true); + } + Object bundleEntry = BUNDLE_ENTRY_FIELD.get(con); + if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.FileBundleEntry")) + { + if (FILE_FIELD == null) + { + FILE_FIELD = bundleEntry.getClass().getDeclaredField("file"); + FILE_FIELD.setAccessible(true); + } + File f = (File) FILE_FIELD.get(bundleEntry); + return f.getParentFile().getParentFile(); + } + else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.ZipBundleEntry")) + { + url = bundle.getEntry("/"); + + con = url.openConnection(); + con.setDefaultUseCaches(Resource.getDefaultUseCaches()); + + if (BUNDLE_ENTRY_FIELD == null) + {// this one will be a DirZipBundleEntry + BUNDLE_ENTRY_FIELD = con.getClass().getDeclaredField("bundleEntry"); + BUNDLE_ENTRY_FIELD.setAccessible(true); + } + bundleEntry = BUNDLE_ENTRY_FIELD.get(con); + if (BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY == null) + { + BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY = bundleEntry.getClass().getDeclaredField("bundleFile"); + BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.setAccessible(true); + } + Object zipBundleFile = BUNDLE_FILE_FIELD_FOR_DIR_ZIP_BUNDLE_ENTRY.get(bundleEntry); + if (ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE == null) + { + ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE = zipBundleFile.getClass().getDeclaredField("zipFile"); + ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.setAccessible(true); + } + ZipFile zipFile = (ZipFile) ZIP_FILE_FILED_FOR_ZIP_BUNDLE_FILE.get(zipBundleFile); + return new File(zipFile.getName()); + } + else if (bundleEntry.getClass().getName().equals("org.eclipse.osgi.baseadaptor.bundlefile.DirZipBundleEntry")) + { + // that will not happen as we did ask for the manifest not a + // directory. + } + } + else if ("bundle".equals(url.getProtocol())) + { + // observed this on felix-2.0.0 + String location = bundle.getLocation(); + if (location.startsWith("file:/")) + { + URI uri = new URI(URIUtil.encodePath(location)); + return new File(uri); + } + else if (location.startsWith("file:")) + { + // location defined in the BundleArchive m_bundleArchive + // it is relative to relative to the BundleArchive's + // m_archiveRootDir + File res = new File(location.substring("file:".length())); + if (!res.exists()) { return null; + // Object bundleArchive = getFelixBundleArchive(bundle); + // File archiveRoot = + // getFelixBundleArchiveRootDir(bundleArchive); + // String currentLocation = + // getFelixBundleArchiveCurrentLocation(bundleArchive); + // System.err.println("Got the archive root " + + // archiveRoot.getAbsolutePath() + // + " current location " + currentLocation + + // " is directory ?"); + // res = new File(archiveRoot, currentLocation != null + // ? currentLocation : location.substring("file:".length())); + } + return res; + } + else if (location.startsWith("reference:file:")) + { + location = URLDecoder.decode(location.substring("reference:".length()), "UTF-8"); + File file = new File(location.substring("file:".length())); + return file; + } + else + { + //Resort to introspection on felix: + return getBundleInstallLocationInFelix(bundle); + } + } + return null; + } + + + /** + * Locate a file inside a bundle. + * + * @param bundle + * @param path + * @return file object + * @throws Exception + */ + public File getFileInBundle(Bundle bundle, String path) throws Exception + { + if (path != null && path.length() > 0 && path.charAt(0) == '/') + { + path = path.substring(1); + } + File bundleInstall = getBundleInstallLocation(bundle); + File webapp = path != null && path.length() != 0 ? new File(bundleInstall, path) : bundleInstall; + if (!webapp.exists()) { throw new IllegalArgumentException("Unable to locate " + path + + " inside " + + bundle.getSymbolicName() + + " (" + + (bundleInstall != null ? bundleInstall.getAbsolutePath() : " no_bundle_location ") + + ")"); } + return webapp; + } + + /** + * Helper method equivalent to Bundle#getEntry(String entryPath) except that + * it searches for entries in the fragments by using the Bundle#findEntries + * method. + * + * @param bundle + * @param entryPath + * @return null or all the entries found for that path. + */ + public Enumeration<URL> findEntries(Bundle bundle, String entryPath) + { + int last = entryPath.lastIndexOf('/'); + String path = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(0, last) : "/"; + if (!path.startsWith("/")) + { + path = "/" + path; + } + String pattern = last != -1 && last < entryPath.length() - 2 ? entryPath.substring(last + 1) : entryPath; + Enumeration<URL> enUrls = bundle.findEntries(path, pattern, false); + return enUrls; + } + + /** + * If the bundle is a jar, returns the jar. If the bundle is a folder, look + * inside it and search for jars that it returns. + * <p> + * Good enough for our purpose (TldLocationsCache when it scans for tld + * files inside jars alone. In fact we only support the second situation for + * development purpose where the bundle was imported in pde and the classes + * kept in a jar. + * </p> + * + * @param bundle + * @return The jar(s) file that is either the bundle itself, either the jars + * embedded inside it. + */ + public File[] locateJarsInsideBundle(Bundle bundle) throws Exception + { + File jasperLocation = getBundleInstallLocation(bundle); + if (jasperLocation.isDirectory()) + { + // try to find the jar files inside this folder + ArrayList<File> urls = new ArrayList<File>(); + for (File f : jasperLocation.listFiles()) + { + if (f.getName().endsWith(".jar") && f.isFile()) + { + urls.add(f); + } + else if (f.isDirectory() && f.getName().equals("lib")) + { + for (File f2 : jasperLocation.listFiles()) + { + if (f2.getName().endsWith(".jar") && f2.isFile()) + { + urls.add(f2); + } + } + } + } + return urls.toArray(new File[urls.size()]); + } + else + { + return new File[] { jasperLocation }; + } + } + + // introspection on equinox to invoke the getLocalURL method on + // BundleURLConnection + // equivalent to using the FileLocator without depending on an equinox + // class. + private static Method BUNDLE_URL_CONNECTION_getLocalURL = null; + + private static Method BUNDLE_URL_CONNECTION_getFileURL = null; + + /** + * Only useful for equinox: on felix we get the file:// or jar:// url + * already. Other OSGi implementations have not been tested + * <p> + * Get a URL to the bundle entry that uses a common protocol (i.e. file: + * jar: or http: etc.). + * </p> + * + * @return a URL to the bundle entry that uses a common protocol + */ + public static URL getLocalURL(URL url) + { + if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) + { + try + { + URLConnection conn = url.openConnection(); + conn.setDefaultUseCaches(Resource.getDefaultUseCaches()); + if (BUNDLE_URL_CONNECTION_getLocalURL == null && conn.getClass().getName().equals("org.eclipse.osgi.framework.internal.core.BundleURLConnection")) + { + BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null); + BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true); + } + if (BUNDLE_URL_CONNECTION_getLocalURL != null) { return (URL) BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); } + } + catch (Throwable t) + { + System.err.println("Unable to locate the OSGi url: '" + url + "'."); + t.printStackTrace(); + } + } + return url; + } + + /** + * Only useful for equinox: on felix we get the file:// url already. Other + * OSGi implementations have not been tested + * <p> + * Get a URL to the content of the bundle entry that uses the file: + * protocol. The content of the bundle entry may be downloaded or extracted + * to the local file system in order to create a file: URL. + * + * @return a URL to the content of the bundle entry that uses the file: + * protocol + * </p> + */ + public static URL getFileURL(URL url) + { + if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) + { + try + { + URLConnection conn = url.openConnection(); + conn.setDefaultUseCaches(Resource.getDefaultUseCaches()); + if (BUNDLE_URL_CONNECTION_getFileURL == null && conn.getClass().getName().equals("org.eclipse.osgi.framework.internal.core.BundleURLConnection")) + { + BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null); + BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true); + } + if (BUNDLE_URL_CONNECTION_getFileURL != null) { return (URL) BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); } + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + return url; + } + + // Felix introspection + private static Method Felix_BundleImpl_getArchive_method; + private static Method Felix_BundleArchive_getCurrentRevision_method; + private static Method Felix_BundleRevision_getRevisionRootDir_method; + + private static boolean felixIntroSpectionDone = false; + + /** + * Introspection of the implementation classes of Felix-3.x and Felix-4.x. + * <p> + * See org.apache.felix.framework.cache + * In pseudo code: + * <code> + * File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir(); + * return new File(revRootDir, bundle.jar) if it exists? + * else return revRootDir + * </p> + * @param bundle + * @return The File or null if we failed to find it. + */ + private static File getBundleInstallLocationInFelix(Bundle bundle) + { + if (Felix_BundleImpl_getArchive_method == null) { + if (felixIntroSpectionDone) + { + return null; + } + felixIntroSpectionDone = true; + try + { + Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {}); + Felix_BundleImpl_getArchive_method.setAccessible(true); + Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); + Class bundleArchiveClass = archive.getClass(); + Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {}); + Felix_BundleArchive_getCurrentRevision_method.setAccessible(true); + Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); + Class bundleRevisionClass = revision.getClass(); + Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {}); + Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true); + } + catch (Throwable t) + { + //nevermind? + //t.printStackTrace(); + Felix_BundleImpl_getArchive_method = null; + return null; + } + } + if (Felix_BundleImpl_getArchive_method != null) + { + try + { + Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); + Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); + File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision); + //System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath()); + File bundleJar = new File(revRootDir, "bundle.jar"); + if (bundleJar.exists()) + { + //bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision + //when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already + //taken care if that scheme earlier. + return bundleJar; + } + else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists()) + { + //this is a DirectoryRevision + return revRootDir; + } + } + catch (Throwable t) + { + //best effort: nevermind + //t.printStackTrace(); + } + } + return null; + } +// -- end Felix introspection + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java new file mode 100644 index 0000000000..384b392c9a --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/old-org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java @@ -0,0 +1,383 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils.internal; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.service.startlevel.StartLevel; + +/** + * When the PackageAdmin service is activated we can look for the fragments + * attached to this bundle and "activate" them. + */ +public class PackageAdminServiceTracker implements ServiceListener +{ + private BundleContext _context; + + private List<BundleActivator> _activatedFragments = new ArrayList<BundleActivator>(); + + private boolean _fragmentsWereActivated = false; + + // Use the deprecated StartLevel to stay compatible with older versions of + // OSGi. + private StartLevel _startLevel; + + private int _maxStartLevel = 6; + + public static PackageAdminServiceTracker INSTANCE = null; + + public PackageAdminServiceTracker(BundleContext context) + { + INSTANCE = this; + _context = context; + if (!setup()) + { + try + { + _context.addServiceListener(this, "(objectclass=" + PackageAdmin.class.getName() + ")"); + } + catch (InvalidSyntaxException e) + { + e.printStackTrace(); // won't happen + } + } + } + + /** + * @return true if the fragments were activated by this method. + */ + private boolean setup() + { + ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName()); + _fragmentsWereActivated = sr != null; + if (sr != null) invokeFragmentActivators(sr); + + sr = _context.getServiceReference(StartLevel.class.getName()); + if (sr != null) + { + _startLevel = (StartLevel) _context.getService(sr); + try + { + _maxStartLevel = Integer.parseInt(System.getProperty("osgi.startLevel", "6")); + } + catch (Exception e) + { + // nevermind default on the usual. + _maxStartLevel = 6; + } + } + return _fragmentsWereActivated; + } + + /** + * Invokes the optional BundleActivator in each fragment. By convention the + * bundle activator for a fragment must be in the package that is defined by + * the symbolic name of the fragment and the name of the class must be + * 'FragmentActivator'. + * + * @param event The <code>ServiceEvent</code> object. + */ + public void serviceChanged(ServiceEvent event) + { + if (event.getType() == ServiceEvent.REGISTERED) + { + invokeFragmentActivators(event.getServiceReference()); + } + } + + /** + * Helper to access the PackageAdmin and return the fragments hosted by a + * bundle. when we drop the support for the older versions of OSGi, we will + * stop using the PackageAdmin service. + * + * @param bundle + * @return + */ + public Bundle[] getFragments(Bundle bundle) + { + ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName()); + if (sr == null) + {// we should never be here really. + return null; + } + PackageAdmin admin = (PackageAdmin) _context.getService(sr); + return admin.getFragments(bundle); + } + + /** + * Returns the fragments and the required-bundles of a bundle. Recursively + * collect the required-bundles and fragment when the directive + * visibility:=reexport is added to a required-bundle. + * + * @param bundle + * @param webFragOrAnnotationOrResources + * @return + */ + public Bundle[] getFragmentsAndRequiredBundles(Bundle bundle) + { + ServiceReference sr = _context.getServiceReference(PackageAdmin.class.getName()); + if (sr == null) + {// we should never be here really. + return null; + } + PackageAdmin admin = (PackageAdmin) _context.getService(sr); + LinkedHashMap<String, Bundle> deps = new LinkedHashMap<String, Bundle>(); + collectFragmentsAndRequiredBundles(bundle, admin, deps, false); + return deps.values().toArray(new Bundle[deps.size()]); + } + + /** + * Returns the fragments and the required-bundles. Collects them + * transitively when the directive 'visibility:=reexport' is added to a + * required-bundle. + * + * @param bundle + * @param webFragOrAnnotationOrResources + * @return + */ + protected void collectFragmentsAndRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String, Bundle> deps, boolean onlyReexport) + { + Bundle[] fragments = admin.getFragments(bundle); + if (fragments != null) + { + // Also add the bundles required by the fragments. + // this way we can inject onto an existing web-bundle a set of + // bundles that extend it + for (Bundle f : fragments) + { + if (!deps.keySet().contains(f.getSymbolicName())) + { + deps.put(f.getSymbolicName(), f); + collectRequiredBundles(f, admin, deps, onlyReexport); + } + } + } + collectRequiredBundles(bundle, admin, deps, onlyReexport); + } + + /** + * A simplistic but good enough parser for the Require-Bundle header. Parses + * the version range attribute and the visibility directive. + * + * @param onlyReexport true to collect resources and web-fragments + * transitively if and only if the directive visibility is + * reexport. + * @param bundle + * @return The map of required bundles associated to the value of the + * jetty-web attribute. + */ + protected void collectRequiredBundles(Bundle bundle, PackageAdmin admin, Map<String, Bundle> deps, boolean onlyReexport) + { + String requiredBundleHeader = (String) bundle.getHeaders().get("Require-Bundle"); + if (requiredBundleHeader == null) { return; } + StringTokenizer tokenizer = new ManifestTokenizer(requiredBundleHeader); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken().trim(); + StringTokenizer tokenizer2 = new StringTokenizer(tok, ";"); + String symbolicName = tokenizer2.nextToken().trim(); + if (deps.keySet().contains(symbolicName)) + { + // was already added. 2 dependencies pointing at the same + // bundle. + continue; + } + String versionRange = null; + boolean reexport = false; + while (tokenizer2.hasMoreTokens()) + { + String next = tokenizer2.nextToken().trim(); + if (next.startsWith("bundle-version=")) + { + if (next.startsWith("bundle-version=\"") || next.startsWith("bundle-version='")) + { + versionRange = next.substring("bundle-version=\"".length(), next.length() - 1); + } + else + { + versionRange = next.substring("bundle-version=".length()); + } + } + else if (next.equals("visibility:=reexport")) + { + reexport = true; + } + } + if (!reexport && onlyReexport) { return; } + Bundle[] reqBundles = admin.getBundles(symbolicName, versionRange); + if (reqBundles != null && reqBundles.length != 0) + { + Bundle reqBundle = null; + for (Bundle b : reqBundles) + { + if (b.getState() == Bundle.ACTIVE || b.getState() == Bundle.STARTING) + { + reqBundle = b; + break; + } + } + if (reqBundle == null) + { + // strange? in OSGi with Require-Bundle, + // the dependent bundle is supposed to be active already + reqBundle = reqBundles[0]; + } + deps.put(reqBundle.getSymbolicName(), reqBundle); + collectFragmentsAndRequiredBundles(reqBundle, admin, deps, true); + } + } + } + + private void invokeFragmentActivators(ServiceReference sr) + { + PackageAdmin admin = (PackageAdmin) _context.getService(sr); + Bundle[] fragments = admin.getFragments(_context.getBundle()); + if (fragments == null) { return; } + for (Bundle frag : fragments) + { + // find a convention to look for a class inside the fragment. + try + { + String fragmentActivator = frag.getSymbolicName() + ".FragmentActivator"; + Class<?> c = Class.forName(fragmentActivator); + if (c != null) + { + BundleActivator bActivator = (BundleActivator) c.newInstance(); + bActivator.start(_context); + _activatedFragments.add(bActivator); + } + } + catch (NullPointerException e) + { + // e.printStackTrace(); + } + catch (InstantiationException e) + { + // e.printStackTrace(); + } + catch (IllegalAccessException e) + { + // e.printStackTrace(); + } + catch (ClassNotFoundException e) + { + // e.printStackTrace(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + public void stop() + { + INSTANCE = null; + for (BundleActivator fragAct : _activatedFragments) + { + try + { + fragAct.stop(_context); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + /** + * @return true if the framework has completed all the start levels. + */ + public boolean frameworkHasCompletedAutostarts() + { + return _startLevel == null ? true : _startLevel.getStartLevel() >= _maxStartLevel; + } + + private static class ManifestTokenizer extends StringTokenizer + { + + public ManifestTokenizer(String header) + { + super(header, ","); + } + + @Override + public String nextToken() + { + String token = super.nextToken(); + + while (hasOpenQuote(token) && hasMoreTokens()) + { + token += "," + super.nextToken(); + } + return token; + } + + private boolean hasOpenQuote(String token) + { + int i = -1; + do + { + int quote = getQuote(token, i + 1); + if (quote < 0) { return false; } + + i = token.indexOf(quote, i + 1); + i = token.indexOf(quote, i + 1); + } + while (i >= 0); + return true; + } + + private int getQuote(String token, int offset) + { + int i = token.indexOf('"', offset); + int j = token.indexOf('\'', offset); + if (i < 0) + { + if (j < 0) + { + return -1; + } + else + { + return '\''; + } + } + if (j < 0) { return '"'; } + if (i < j) { return '"'; } + return '\''; + } + + } + +} + diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java index 98ac22b6eb..b4ae909819 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationConfiguration.java @@ -18,12 +18,16 @@ package org.eclipse.jetty.osgi.annotations; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.ClassNameResolver; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java new file mode 100644 index 0000000000..6f07480ab6 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractContextProvider.java @@ -0,0 +1,360 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.osgi.boot.utils.OSGiClassLoader; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.JarResource; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + + +/** + * AbstractContextProvider + * + * + */ +public abstract class AbstractContextProvider extends AbstractLifeCycle implements AppProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + private DeploymentManager _deploymentManager; + + + private ServerInstanceWrapper _serverWrapper; + + + + + /* ------------------------------------------------------------ */ + /** + * BundleApp + * + * + */ + public class OSGiApp extends AbstractOSGiApp + { + private String _contextFile; + private ContextHandler _contextHandler; + private boolean _configured = false; + + public OSGiApp(DeploymentManager manager, AppProvider provider, String originId, Bundle bundle, String contextFile) + { + super(manager, provider, bundle, originId); + _contextFile = contextFile; + } + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String contextFile, String originId) + { + super(manager, provider, bundle, properties, originId); + _contextFile = contextFile; + } + + public String getContextFile () + { + return _contextFile; + } + + public void setHandler(ContextHandler h) + { + _contextHandler = h; + } + + public ContextHandler createContextHandler() + throws Exception + { + configureContextHandler(); + return _contextHandler; + } + + public void configureContextHandler() + throws Exception + { + if (_configured) + return; + + _configured = true; + + //Override for bundle root may have been set + String bundleOverrideLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleOverrideLocation == null) + bundleOverrideLocation = (String)_properties.get(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE); + + //Location on filesystem of bundle or the bundle override location + File bundleLocation = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle); + File root = (bundleOverrideLocation==null?bundleLocation:new File(bundleOverrideLocation)); + Resource rootResource = Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(root.toURI().toURL())); + + //try and make sure the rootResource is useable - if its a jar then make it a jar file url + if (rootResource.exists()&& !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:")) + { + Resource jarResource = JarResource.newJarResource(rootResource); + if (jarResource.exists() && jarResource.isDirectory()) + rootResource = jarResource; + } + + //Set the base resource of the ContextHandler, if not already set, can also be overridden by the context xml file + if (_contextHandler != null && _contextHandler.getBaseResource() == null) + { + _contextHandler.setBaseResource(rootResource); + } + + //Use a classloader that knows about the common jetty parent loader, and also the bundle + OSGiClassLoader classLoader = new OSGiClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps(), _bundle); + + //if there is a context file, find it and apply it + if (_contextFile == null && _contextHandler == null) + throw new IllegalStateException("No context file or ContextHandler"); + + if (_contextFile != null) + { + //apply the contextFile, creating the ContextHandler, the DeploymentManager will register it in the ContextHandlerCollection + Resource res = null; + + //try to find the context file in the filesystem + if (_contextFile.startsWith("/")) + res = getFileAsResource(_contextFile); + + //try to find it relative to jetty home + if (res == null) + { + //See if the specific server we are related to has jetty.home set + String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME); + if (jettyHome != null) + res = getFileAsResource(jettyHome, _contextFile); + + //try to see if a SystemProperty for jetty.home is set + if (res == null) + { + jettyHome = System.getProperty(OSGiServerConstants.JETTY_HOME); + + if (jettyHome != null) + { + if (jettyHome.startsWith("\"") || jettyHome.startsWith("'")) + jettyHome = jettyHome.substring(1); + if (jettyHome.endsWith("\"") || (jettyHome.endsWith("'"))) + jettyHome = jettyHome.substring(0,jettyHome.length()-1); + + res = getFileAsResource(jettyHome, _contextFile); + if (LOG.isDebugEnabled()) LOG.debug("jetty home context file:"+res); + } + } + } + + //try to find it relative to an override location that has been specified + if (res == null) + { + if (bundleOverrideLocation != null) + { + res = getFileAsResource(Resource.newResource(bundleOverrideLocation).getFile(), _contextFile); + if (LOG.isDebugEnabled()) LOG.debug("Bundle override location context file:"+res); + } + } + + //try to find it relative to the bundle in which it is being deployed + if (res == null) + { + if (_contextFile.startsWith("./")) + _contextFile = _contextFile.substring(1); + + if (!_contextFile.startsWith("/")) + _contextFile = "/" + _contextFile; + + URL contextURL = _bundle.getEntry(_contextFile); + if (contextURL != null) + res = Resource.newResource(contextURL); + } + + //apply the context xml file, either to an existing ContextHandler, or letting the + //it create the ContextHandler as necessary + if (res != null) + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + LOG.debug("Context classloader = " + cl); + try + { + Thread.currentThread().setContextClassLoader(classLoader); + + XmlConfiguration xmlConfiguration = new XmlConfiguration(res.getInputStream()); + HashMap properties = new HashMap(); + //put the server instance in + properties.put("Server", getServerInstanceWrapper().getServer()); + //put in the location of the bundle root + properties.put("bundle.root", rootResource.toString()); + + // insert the bundle's location as a property. + xmlConfiguration.getProperties().putAll(properties); + + if (_contextHandler == null) + _contextHandler = (ContextHandler) xmlConfiguration.configure(); + else + xmlConfiguration.configure(_contextHandler); + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + } + + //Set up the class loader we created + _contextHandler.setClassLoader(classLoader); + + + //If a bundle/service property specifies context path, let it override the context xml + String contextPath = (String)_properties.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + contextPath = (String)_properties.get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); + if (contextPath != null) + _contextHandler.setContextPath(contextPath); + + //osgi Enterprise Spec r4 p.427 + _contextHandler.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext()); + + //make sure we protect also the osgi dirs specified by OSGi Enterprise spec + String[] targets = _contextHandler.getProtectedTargets(); + int length = (targets==null?0:targets.length); + + String[] updatedTargets = null; + if (targets != null) + { + updatedTargets = new String[length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(targets, 0, updatedTargets, 0, length); + + } + else + updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length); + _contextHandler.setProtectedTargets(updatedTargets); + + } + + + private Resource getFileAsResource (String dir, String file) + { + Resource r = null; + try + { + File asFile = new File (dir, file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + + private Resource getFileAsResource (String file) + { + Resource r = null; + try + { + File asFile = new File (file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + + private Resource getFileAsResource (File dir, String file) + { + Resource r = null; + try + { + File asFile = new File (dir, file); + if (asFile.exists()) + r = Resource.newResource(asFile); + } + catch (Exception e) + { + r = null; + } + return r; + } + } + + /* ------------------------------------------------------------ */ + public AbstractContextProvider(ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + + /* ------------------------------------------------------------ */ + public ServerInstanceWrapper getServerInstanceWrapper() + { + return _serverWrapper; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.deploy.AppProvider#createContextHandler(org.eclipse.jetty.deploy.App) + */ + public ContextHandler createContextHandler(App app) throws Exception + { + if (app == null) + return null; + if (!(app instanceof OSGiApp)) + throw new IllegalStateException(app+" is not a BundleApp"); + + //Create a ContextHandler suitable to deploy in OSGi + ContextHandler h = ((OSGiApp)app).createContextHandler(); + return h; + } + + /* ------------------------------------------------------------ */ + public void setDeploymentManager(DeploymentManager deploymentManager) + { + _deploymentManager = deploymentManager; + } + + /* ------------------------------------------------------------ */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java new file mode 100644 index 0000000000..be09d2cfd6 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractOSGiApp.java @@ -0,0 +1,120 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + +/** + * AbstractBundleApp + * + * + */ +public abstract class AbstractOSGiApp extends App +{ + protected Bundle _bundle; + protected Dictionary _properties; + protected ServiceRegistration _registration; + + /* ------------------------------------------------------------ */ + public AbstractOSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, originId); + _properties = bundle.getHeaders(); + _bundle = bundle; + } + /* ------------------------------------------------------------ */ + public AbstractOSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, originId); + _properties = properties; + _bundle = bundle; + } + + /* ------------------------------------------------------------ */ + public String getBundleSymbolicName() + { + return _bundle.getSymbolicName(); + } + + /* ------------------------------------------------------------ */ + public String getBundleVersionAsString() + { + if (_bundle.getVersion() == null) + return null; + return _bundle.getVersion().toString(); + } + + /* ------------------------------------------------------------ */ + public Bundle getBundle() + { + return _bundle; + } + + /* ------------------------------------------------------------ */ + public void setRegistration (ServiceRegistration registration) + { + _registration = registration; + } + + /* ------------------------------------------------------------ */ + public ServiceRegistration getRegistration () + { + return _registration; + } + + + /* ------------------------------------------------------------ */ + public void registerAsOSGiService() throws Exception + { + if (_registration == null) + { + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK); + if (getBundleSymbolicName() != null) + properties.put(OSGiWebappConstants.OSGI_WEB_SYMBOLICNAME, getBundleSymbolicName()); + if (getBundleVersionAsString() != null) + properties.put(OSGiWebappConstants.OSGI_WEB_VERSION, getBundleVersionAsString()); + properties.put(OSGiWebappConstants.OSGI_WEB_CONTEXTPATH, getContextPath()); + ServiceRegistration rego = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ContextHandler.class.getName(), getContextHandler(), properties); + setRegistration(rego); + } + } + + /* ------------------------------------------------------------ */ + protected void deregisterAsOSGiService() throws Exception + { + if (_registration == null) + return; + + _registration.unregister(); + _registration = null; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java new file mode 100644 index 0000000000..73da20f41c --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java @@ -0,0 +1,552 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.packageadmin.PackageAdmin; + + + + +/** + * AbstractWebAppProvider + * + * + */ +public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + public static String __defaultConfigurations[] = { + "org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration", + "org.eclipse.jetty.webapp.WebXmlConfiguration", + "org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration", + "org.eclipse.jetty.webapp.FragmentConfiguration", + "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//, + //"org.eclipse.jetty.osgi.boot.jsp.TagLibOSGiConfiguration" + }; + + public static void setDefaultConfigurations (String[] defaultConfigs) + { + __defaultConfigurations = defaultConfigs; + } + + public static String[] getDefaultConfigurations () + { + return __defaultConfigurations; + } + + + private boolean _parentLoaderPriority; + + private String _defaultsDescriptor; + + private boolean _extractWars = true; //See WebAppContext.extractWars + + private String _tldBundles; + + private DeploymentManager _deploymentManager; + + private String[] _configurationClasses; + + private ServerInstanceWrapper _serverWrapper; + + /* ------------------------------------------------------------ */ + /** + * OSGiApp + * + * + */ + public class OSGiApp extends AbstractOSGiApp + { + private String _contextPath; + private String _webAppPath; + private WebAppContext _webApp; + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, bundle, originId); + } + + public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, bundle, properties, originId); + } + + public void setWebAppContext (WebAppContext webApp) + { + _webApp = webApp; + } + + public String getContextPath() + { + return _contextPath; + } + + public void setContextPath(String contextPath) + { + this._contextPath = contextPath; + } + + public String getBundlePath() + { + return _webAppPath; + } + + public void setWebAppPath(String path) + { + this._webAppPath = path; + } + + + public ContextHandler createContextHandler() + throws Exception + { + if (_webApp != null) + { + configureWebApp(); + return _webApp; + } + + createWebApp(); + return _webApp; + } + + + + protected void createWebApp () + throws Exception + { + _webApp = newWebApp(); + configureWebApp(); + } + + protected WebAppContext newWebApp () + { + WebAppContext webApp = new WebAppContext(); + webApp.setAttribute(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK); + + //make sure we protect also the osgi dirs specified by OSGi Enterprise spec + String[] targets = webApp.getProtectedTargets(); + String[] updatedTargets = null; + if (targets != null) + { + updatedTargets = new String[targets.length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(targets, 0, updatedTargets, 0, targets.length); + } + else + updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length]; + System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, targets.length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length); + webApp.setProtectedTargets(updatedTargets); + + return webApp; + } + + + public void configureWebApp() + throws Exception + { + //TODO turn this around and let any context.xml file get applied first, and have the properties override + _webApp.setContextPath(_contextPath); + + //osgi Enterprise Spec r4 p.427 + _webApp.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext()); + + String overrideBundleInstallLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + File bundleInstallLocation = + (overrideBundleInstallLocation == null + ? BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle) + : new File(overrideBundleInstallLocation)); + URL url = null; + + //if the path wasn't set or it was ., then it is the root of the bundle's installed location + if (_webAppPath == null || _webAppPath.length() == 0 || ".".equals(_webAppPath)) + { + url = bundleInstallLocation.toURI().toURL(); + } + else + { + //Get the location of the root of the webapp inside the installed bundle + if (_webAppPath.startsWith("/") || _webAppPath.startsWith("file:")) + { + url = new File(_webAppPath).toURI().toURL(); + } + else if (bundleInstallLocation != null && bundleInstallLocation.isDirectory()) + { + url = new File(bundleInstallLocation, _webAppPath).toURI().toURL(); + } + else if (bundleInstallLocation != null) + { + Enumeration<URL> urls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(_bundle, _webAppPath); + if (urls != null && urls.hasMoreElements()) + url = urls.nextElement(); + } + } + + if (url == null) + { + throw new IllegalArgumentException("Unable to locate " + _webAppPath + + " in " + + (bundleInstallLocation != null ? bundleInstallLocation.getAbsolutePath() : "unlocated bundle '" + _bundle.getSymbolicName()+ "'")); + } + + // converts bundleentry: protocol if necessary + _webApp.setWar(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url).toString()); + + // Set up what has been configured on the provider + _webApp.setParentLoaderPriority(isParentLoaderPriority()); + _webApp.setExtractWAR(isExtract()); + if (getConfigurationClasses() != null) + _webApp.setConfigurationClasses(getConfigurationClasses()); + else + _webApp.setConfigurationClasses(__defaultConfigurations); + + if (getDefaultsDescriptor() != null) + _webApp.setDefaultsDescriptor(getDefaultsDescriptor()); + + //Set up configuration from manifest headers + //extra classpath + String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH); + if (tmp != null) + _webApp.setExtraClasspath(tmp); + + //web.xml + tmp = (String)_properties.get(OSGiWebappConstants.JETTY_WEB_XML_PATH); + if (tmp != null && tmp.trim().length() != 0) + { + File webXml = getFile (tmp, bundleInstallLocation); + if (webXml != null && webXml.exists()) + _webApp.setDescriptor(webXml.getAbsolutePath()); + } + + //webdefault.xml + tmp = (String)_properties.get(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH); + if (tmp != null) + { + File defaultWebXml = getFile (tmp, bundleInstallLocation); + if (defaultWebXml != null && defaultWebXml.exists()) + _webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath()); + } + + //Handle Require-TldBundle + //This is a comma separated list of names of bundles that contain tlds that this webapp uses. + //We add them to the webapp classloader. + String requireTldBundles = (String)_properties.get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + String pathsToTldBundles = getPathsToRequiredBundles(requireTldBundles); + + + // make sure we provide access to all the jetty bundles by going + // through this bundle. + OSGiWebappClassLoader webAppLoader = new OSGiWebappClassLoader(_serverWrapper.getParentClassLoaderForWebapps(), _webApp, _bundle); + + if (pathsToTldBundles != null) + webAppLoader.addClassPath(pathsToTldBundles); + _webApp.setClassLoader(webAppLoader); + + + // apply any META-INF/context.xml file that is found to configure + // the webapp first + applyMetaInfContextXml(); + + // pass the value of the require tld bundle so that the TagLibOSGiConfiguration + // can pick it up. + _webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles); + + //Set up some attributes + // rfc66 + _webApp.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT, _bundle.getBundleContext()); + + // spring-dm-1.2.1 looks for the BundleContext as a different attribute. + // not a spec... but if we want to support + // org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext + // then we need to do this to: + _webApp.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), _bundle.getBundleContext()); + + // also pass the bundle directly. sometimes a bundle does not have a + // bundlecontext. + // it is still useful to have access to the Bundle from the servlet + // context. + _webApp.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, _bundle); + } + + protected String getPathsToRequiredBundles (String requireTldBundles) + throws Exception + { + if (requireTldBundles == null) return null; + + ServiceReference ref = _bundle.getBundleContext().getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName()); + PackageAdmin packageAdmin = (ref == null) ? null : (PackageAdmin)_bundle.getBundleContext().getService(ref); + if (packageAdmin == null) + throw new IllegalStateException("Unable to get PackageAdmin reference to locate required Tld bundles"); + + StringBuilder paths = new StringBuilder(); + String[] symbNames = requireTldBundles.split(", "); + + for (String symbName : symbNames) + { + Bundle[] bs = packageAdmin.getBundles(symbName, null); + if (bs == null || bs.length == 0) + { + throw new IllegalArgumentException("Unable to locate the bundle '" + symbName + + "' specified by " + + OSGiWebappConstants.REQUIRE_TLD_BUNDLE + + " in manifest of " + + (_bundle == null ? "unknown" : _bundle.getSymbolicName())); + } + + File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bs[0]); + if (paths.length() > 0) paths.append(", "); + paths.append(f.toURI().toURL().toString()); + LOG.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI()); + } + + return paths.toString(); + } + + + protected void applyMetaInfContextXml() + throws Exception + { + if (_bundle == null) return; + if (_webApp == null) return; + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + LOG.debug("Context classloader = " + cl); + try + { + + Thread.currentThread().setContextClassLoader(_webApp.getClassLoader()); + + //TODO replace this with getting the InputStream so we don't cache in URL + // find if there is a META-INF/context.xml file + URL contextXmlUrl = _bundle.getEntry("/META-INF/jetty-webapp-context.xml"); + if (contextXmlUrl == null) return; + + // Apply it just as the standard jetty ContextProvider would do + LOG.info("Applying " + contextXmlUrl + " to " + _webApp); + + XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl); + HashMap properties = new HashMap(); + properties.put("Server", getDeploymentManager().getServer()); + xmlConfiguration.getProperties().putAll(properties); + xmlConfiguration.configure(_webApp); + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + private File getFile (String file, File bundleInstall) + { + if (file == null) + return null; + + if (file.startsWith("/") || file.startsWith("file:/")) + return new File(file); + else + return new File(bundleInstall, file); + } + } + + /* ------------------------------------------------------------ */ + public AbstractWebAppProvider (ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + + + /* ------------------------------------------------------------ */ + /** + * Get the parentLoaderPriority. + * + * @return the parentLoaderPriority + */ + public boolean isParentLoaderPriority() + { + return _parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + /** + * Set the parentLoaderPriority. + * + * @param parentLoaderPriority the parentLoaderPriority to set + */ + public void setParentLoaderPriority(boolean parentLoaderPriority) + { + _parentLoaderPriority = parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + /** + * Get the defaultsDescriptor. + * + * @return the defaultsDescriptor + */ + public String getDefaultsDescriptor() + { + return _defaultsDescriptor; + } + + /* ------------------------------------------------------------ */ + /** + * Set the defaultsDescriptor. + * + * @param defaultsDescriptor the defaultsDescriptor to set + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + _defaultsDescriptor = defaultsDescriptor; + } + + + /* ------------------------------------------------------------ */ + public boolean isExtract() + { + return _extractWars; + } + + + /* ------------------------------------------------------------ */ + public void setExtract(boolean extract) + { + _extractWars = extract; + } + + + /* ------------------------------------------------------------ */ + /** + * @param tldBundles Comma separated list of bundles that contain tld jars + * that should be setup on the jetty instances created here. + */ + public void setTldBundles(String tldBundles) + { + _tldBundles = tldBundles; + } + + + /* ------------------------------------------------------------ */ + /** + * @return The list of bundles that contain tld jars that should be setup on + * the jetty instances created here. + */ + public String getTldBundles() + { + return _tldBundles; + } + + /* ------------------------------------------------------------ */ + /** + * @param configurations The configuration class names. + */ + public void setConfigurationClasses(String[] configurations) + { + _configurationClasses = configurations == null ? null : (String[]) configurations.clone(); + } + + /* ------------------------------------------------------------ */ + /** + * + */ + public String[] getConfigurationClasses() + { + return _configurationClasses; + } + + /* ------------------------------------------------------------ */ + public void setServerInstanceWrapper(ServerInstanceWrapper wrapper) + { + _serverWrapper = wrapper; + } + + public ServerInstanceWrapper getServerInstanceWrapper() + { + return _serverWrapper; + } + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.deploy.AppProvider#setDeploymentManager(org.eclipse.jetty.deploy.DeploymentManager) + */ + public void setDeploymentManager(DeploymentManager deploymentManager) + { + _deploymentManager = deploymentManager; + } + + + /* ------------------------------------------------------------ */ + public ContextHandler createContextHandler(App app) throws Exception + { + if (app == null) + return null; + if (!(app instanceof OSGiApp)) + throw new IllegalStateException(app+" is not a BundleApp"); + + //Create a WebAppContext suitable to deploy in OSGi + ContextHandler ch = ((OSGiApp)app).createContextHandler(); + return ch; + } + + + /* ------------------------------------------------------------ */ + public static String getOriginId(Bundle contributor, String path) + { + return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + (path.startsWith("/") ? path : "/" + path); + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java new file mode 100644 index 0000000000..149aa99b80 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleContextProvider.java @@ -0,0 +1,169 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + + + +/** + * BundleContextProvider + * + * Handles deploying bundles that define a context xml file for configuring them. + * + * + */ +public class BundleContextProvider extends AbstractContextProvider implements BundleProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + + private Map<String, App> _appMap = new HashMap<String, App>(); + + private Map<Bundle, List<App>> _bundleMap = new HashMap<Bundle, List<App>>(); + + private ServiceRegistration _serviceRegForBundles; + + + + + /* ------------------------------------------------------------ */ + public BundleContextProvider(ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + //register as an osgi service for deploying contexts defined in a bundle, advertising the name of the jetty Server instance we are related to + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + _serviceRegForBundles = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(BundleProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForBundles != null) + { + try + { + _serviceRegForBundles.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + } + + + + + /* ------------------------------------------------------------ */ + /** + * @param bundle + * @param contextFiles + * @return + */ + public boolean bundleAdded (Bundle bundle) throws Exception + { + if (bundle == null) + return false; + + String contextFiles = (String)bundle.getHeaders().get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); + if (contextFiles == null) + contextFiles = (String)bundle.getHeaders().get(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + + if (contextFiles == null) + return false; + + boolean added = false; + //bundle defines JETTY_CONTEXT_FILE_PATH header, + //a comma separated list of context xml files that each define a ContextHandler + //TODO: (could be WebAppContexts) + String[] tmp = contextFiles.split(",;"); + for (String contextFile : tmp) + { + String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile; + OSGiApp app = new OSGiApp(getDeploymentManager(), this, originId, bundle, contextFile); + _appMap.put(originId,app); + List<App> apps = _bundleMap.get(bundle); + if (apps == null) + { + apps = new ArrayList<App>(); + _bundleMap.put(bundle, apps); + } + apps.add(app); + getDeploymentManager().addApp(app); + } + + return added; //true if even 1 context from this bundle was added + } + + + /* ------------------------------------------------------------ */ + /** + * Bundle has been removed. If it was a context we deployed, undeploy it. + * @param bundle + * + * @return true if this was a context we had deployed, false otherwise + */ + public boolean bundleRemoved (Bundle bundle) throws Exception + { + List<App> apps = _bundleMap.remove(bundle); + boolean removed = false; + if (apps != null) + { + for (App app:apps) + { + _appMap.remove(app.getOriginId()); + getDeploymentManager().removeApp(app); + removed = true; + } + } + return removed; //true if even 1 context was removed associated with this bundle + } + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java new file mode 100644 index 0000000000..c87c071bf5 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleProvider.java @@ -0,0 +1,28 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.osgi.framework.Bundle; + +public interface BundleProvider +{ + public boolean bundleAdded (Bundle bundle) throws Exception; + + public boolean bundleRemoved (Bundle bundle) throws Exception; +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java new file mode 100644 index 0000000000..2a5e6e3cd0 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/BundleWebAppProvider.java @@ -0,0 +1,238 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; + + + +/** + * BundleWebAppProvider + * + * A Jetty Provider that knows how to deploy a WebApp contained inside a Bundle. + * + */ +public class BundleWebAppProvider extends AbstractWebAppProvider implements BundleProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + /** + * Map of Bundle to App. Used when a Bundle contains a webapp. + */ + private Map<Bundle, App> _bundleMap = new HashMap<Bundle, App>(); + + private ServiceRegistration _serviceRegForBundles; + + + /* ------------------------------------------------------------ */ + /** + * @param wrapper + */ + public BundleWebAppProvider (ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + //register as an osgi service for deploying bundles, advertising the name of the jetty Server instance we are related to + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + _serviceRegForBundles = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(BundleProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForBundles != null) + { + try + { + _serviceRegForBundles.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + + super.doStop(); + } + + + + + + + /* ------------------------------------------------------------ */ + /** + * A bundle has been added that could be a webapp + * @param bundle + */ + public boolean bundleAdded (Bundle bundle) throws Exception + { + if (bundle == null) + return false; + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + String contextPath = null; + try + { + Dictionary headers = bundle.getHeaders(); + + //does the bundle have a OSGiWebappConstants.JETTY_WAR_FOLDER_PATH + if (headers.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH) != null) + { + String base = (String)headers.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); + contextPath = getContextPath(bundle); + String originId = getOriginId(bundle, base); + + //TODO : we don't know whether an app is actually deployed, as deploymentManager swallows all + //exceptions inside the impl of addApp. Need to send the Event and also register as a service + //only if the deployment succeeded + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setWebAppPath(base); + app.setContextPath(contextPath); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + + //does the bundle have a WEB-INF/web.xml + if (bundle.getEntry("/WEB-INF/web.xml") != null) + { + String base = "."; + contextPath = getContextPath(bundle); + String originId = getOriginId(bundle, base); + + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + //does the bundle define a OSGiWebappConstants.RFC66_WEB_CONTEXTPATH + if (headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) != null) + { + //Could be a static webapp with no web.xml + String base = "."; + contextPath = (String)headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + String originId = getOriginId(bundle,base); + + OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + _bundleMap.put(bundle, app); + getDeploymentManager().addApp(app); + return true; + } + + return false; + } + catch (Exception e) + { + + throw e; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Bundle has been removed. If it was a webapp we deployed, undeploy it. + * @param bundle + * + * @return true if this was a webapp we had deployed, false otherwise + */ + public boolean bundleRemoved (Bundle bundle) throws Exception + { + App app = _bundleMap.remove(bundle); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + return false; + } + + + + + + /* ------------------------------------------------------------ */ + private static String getContextPath(Bundle bundle) + { + Dictionary<?, ?> headers = bundle.getHeaders(); + String contextPath = (String) headers.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + { + // extract from the last token of the bundle's location: + // (really ?could consider processing the symbolic name as an alternative + // the location will often reflect the version. + // maybe this is relevant when the file is a war) + String location = bundle.getLocation(); + String toks[] = location.replace('\\', '/').split("/"); + contextPath = toks[toks.length - 1]; + // remove .jar, .war etc: + int lastDot = contextPath.lastIndexOf('.'); + if (lastDot != -1) + contextPath = contextPath.substring(0, lastDot); + } + if (!contextPath.startsWith("/")) + contextPath = "/" + contextPath; + + return contextPath; + } + + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java index 777ebdf608..2ed6e7bf5e 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java @@ -66,8 +66,6 @@ public class JettyBootstrapActivator implements BundleActivator private ServiceRegistration _registeredServer; - private Server _server; - private JettyContextHandlerServiceTracker _jettyContextHandlerTracker; private PackageAdminServiceTracker _packageAdminServiceTracker; @@ -95,20 +93,21 @@ public class JettyBootstrapActivator implements BundleActivator // should activate. _packageAdminServiceTracker = new PackageAdminServiceTracker(context); - // track Server instances that we should support as deployment targets + // track jetty Server instances that we should support as deployment targets _jettyServerServiceTracker = new JettyServerServiceTracker(); context.addServiceListener(_jettyServerServiceTracker, "(objectclass=" + Server.class.getName() + ")"); // track ContextHandler class instances and deploy them to one of the known Servers - _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(_jettyServerServiceTracker); + _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(); context.addServiceListener(_jettyContextHandlerTracker, "(objectclass=" + ContextHandler.class.getName() + ")"); // Create a default jetty instance right now. DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context); // track Bundles and deploy those that represent webapps to one of the known Servers - _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, new WebBundleTrackerCustomizer()); - _webBundleTracker.open(); + WebBundleTrackerCustomizer customizer = new WebBundleTrackerCustomizer(); + _webBundleTracker = new BundleTracker(context, Bundle.ACTIVE | Bundle.STOPPING, customizer); + customizer.setAndOpenWebBundleTracker(_webBundleTracker); } /** @@ -129,7 +128,6 @@ public class JettyBootstrapActivator implements BundleActivator } if (_jettyContextHandlerTracker != null) { - _jettyContextHandlerTracker.stop(); context.removeServiceListener(_jettyContextHandlerTracker); _jettyContextHandlerTracker = null; } @@ -163,10 +161,6 @@ public class JettyBootstrapActivator implements BundleActivator } finally { - if (_server != null) - { - _server.stop(); - } INSTANCE = null; } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java new file mode 100644 index 0000000000..5f9321ebc9 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java @@ -0,0 +1,61 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.bindings.StandardDeployer; +import org.eclipse.jetty.deploy.graph.Node; +import org.eclipse.jetty.osgi.boot.utils.EventSender; + + +/** + * OSGiDeployer + * + * + */ +public class OSGiDeployer extends StandardDeployer +{ + + /* ------------------------------------------------------------ */ + public void processBinding(Node node, App app) throws Exception + { + //TODO how to NOT send this event if its not a webapp: + //OSGi Enterprise Spec only wants an event sent if its a webapp bundle (ie not a ContextHandler) + if (!(app instanceof AbstractOSGiApp)) + { + super.processBinding(node,app); + } + else + { + EventSender.getInstance().send(EventSender.DEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + try + { + super.processBinding(node,app); + ((AbstractOSGiApp)app).registerAsOSGiService(); + EventSender.getInstance().send(EventSender.DEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + } + catch (Exception e) + { + EventSender.getInstance().send(EventSender.FAILED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + throw e; + } + } + + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java new file mode 100644 index 0000000000..bacc8ea9a0 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiMetaInfConfiguration.java @@ -0,0 +1,116 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.MetaInfConfiguration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; + +public class OSGiMetaInfConfiguration extends MetaInfConfiguration +{ + private static final Logger LOG = Log.getLogger(OSGiMetaInfConfiguration.class); + + + /** + * Inspect bundle fragments associated with the bundle of the webapp for web-fragment, resources, tlds. + * + * @see org.eclipse.jetty.webapp.MetaInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void preConfigure(final WebAppContext context) throws Exception + { + List<Resource> frags = (List<Resource>) context.getAttribute(METAINF_FRAGMENTS); + List<Resource> resfrags = (List<Resource>) context.getAttribute(METAINF_RESOURCES); + List<Resource> tldfrags = (List<Resource>) context.getAttribute(METAINF_TLDS); + + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE)); + //TODO not convinced we need to do this, as we added any fragment jars to the MetaData.webInfJars in OSGiWebInfConfiguration, + //so surely the web-fragments and resources tlds etc can be discovered normally? + for (Bundle frag : fragments) + { + URL webFrag = frag.getEntry("/META-INF/web-fragment.xml"); + Enumeration<URL> resEnum = frag.findEntries("/META-INF/resources", "*", true); + Enumeration<URL> tldEnum = frag.findEntries("/META-INF", "*.tld", false); + if (webFrag != null || (resEnum != null && resEnum.hasMoreElements()) || (tldEnum != null && tldEnum.hasMoreElements())) + { + try + { + if (webFrag != null) + { + if (frags == null) + { + frags = new ArrayList<Resource>(); + context.setAttribute(METAINF_FRAGMENTS, frags); + } + frags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag).toURI())); + } + if (resEnum != null && resEnum.hasMoreElements()) + { + URL resourcesEntry = frag.getEntry("/META-INF/resources/"); + if (resourcesEntry == null) + { + // probably we found some fragments to a + // bundle. + // those are already contributed. + // so we skip this. + } + else + { + if (resfrags == null) + { + resfrags = new ArrayList<Resource>(); + context.setAttribute(METAINF_RESOURCES, resfrags); + } + resfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(resourcesEntry))); + } + } + if (tldEnum != null && tldEnum.hasMoreElements()) + { + if (tldfrags == null) + { + tldfrags = new ArrayList<Resource>(); + context.setAttribute(METAINF_TLDS, tldfrags); + } + while (tldEnum.hasMoreElements()) + { + URL tldUrl = tldEnum.nextElement(); + tldfrags.add(Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(tldUrl))); + } + } + } + catch (Exception e) + { + LOG.warn("Unable to locate the bundle " + frag.getBundleId(), e); + } + } + } + + super.preConfigure(context); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java index 53b35c7fd6..2f9df55ad8 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java @@ -23,6 +23,38 @@ package org.eclipse.jetty.osgi.boot; */ public class OSGiServerConstants { + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String JETTY_HOME = "jetty.home"; + + /** + * System property to point to a bundle that embeds a jetty configuration + * and that jetty configuration should be the default jetty server. First we + * look for jetty.home. If we don't find it then we look for this property. + */ + public static final String JETTY_HOME_BUNDLE = "jetty.home.bundle"; + + /** + * Usual system property used as the hostname for a typical jetty + * configuration. + */ + public static final String JETTY_HOST = "jetty.host"; + + /** + * Usual system property used as the port for http for a typical jetty + * configuration. + */ + public static final String JETTY_PORT = "jetty.port"; + + /** + * Usual system property used as the port for https for a typical jetty + * configuration. + */ + public static final String JETTY_PORT_SSL = "jetty.port.ssl"; + + //for managed jetty instances, name of the configuration parameters /** * PID of the jetty servers's ManagedFactory diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java new file mode 100644 index 0000000000..ac068741d0 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiUndeployer.java @@ -0,0 +1,44 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.bindings.StandardUndeployer; +import org.eclipse.jetty.deploy.graph.Node; +import org.eclipse.jetty.osgi.boot.utils.EventSender; + + + + +/** + * OSGiUndeployer + * + * + */ +public class OSGiUndeployer extends StandardUndeployer +{ + /* ------------------------------------------------------------ */ + public void processBinding(Node node, App app) throws Exception + { + EventSender.getInstance().send(EventSender.UNDEPLOYING_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + super.processBinding(node,app); + EventSender.getInstance().send(EventSender.UNDEPLOYED_EVENT, ((AbstractOSGiApp)app).getBundle(), app.getContextPath()); + ((AbstractOSGiApp)app).deregisterAsOSGiService(); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java new file mode 100644 index 0000000000..80deac4fb5 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java @@ -0,0 +1,273 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; +import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + + + +/** + * OSGiWebInfConfiguration + * + * Handle adding resources found in bundle fragments, and add them into the + */ +public class OSGiWebInfConfiguration extends WebInfConfiguration +{ + private static final Logger LOG = Log.getLogger(WebInfConfiguration.class); + + + public static final String CONTAINER_BUNDLE_PATTERN = "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"; + + + /** + * Check to see if there have been any bundle symbolic names added of bundles that should be + * regarded as being on the container classpath, and scanned for fragments, tlds etc etc. + * This can be defined in: + * <ol> + * <li>SystemProperty SYS_PROP_TLD_BUNDLES</li> + * <li>DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN</li> + * </ol> + * + * We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds + * MANIFEST.MF header. This is processed in the TagLibOSGiConfiguration class. + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void preConfigure(final WebAppContext context) throws Exception + { + super.preConfigure(context); + + //Check to see if there have been any bundle symbolic names added of bundles that should be + //regarded as being on the container classpath, and scanned for fragments, tlds etc etc. + //This can be defined in: + // 1. SystemProperty SYS_PROP_TLD_BUNDLES + // 2. DeployerManager.setContextAttribute CONTAINER_BUNDLE_PATTERN + String tmp = (String)context.getAttribute(CONTAINER_BUNDLE_PATTERN); + Pattern pattern = (tmp==null?null:Pattern.compile(tmp)); + List<String> names = new ArrayList<String>(); + tmp = System.getProperty("org.eclipse.jetty.osgi.tldbundles"); + if (tmp != null) + { + StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false); + while (tokenizer.hasMoreTokens()) + names.add(tokenizer.nextToken()); + } + + HashSet<Resource> matchingResources = new HashSet<Resource>(); + if ( !names.isEmpty() || pattern != null) + { + Bundle[] bundles = FrameworkUtil.getBundle(OSGiWebInfConfiguration.class).getBundleContext().getBundles(); + + for (Bundle bundle : bundles) + { + if (pattern != null) + { + // if bundle symbolic name matches the pattern + if (pattern.matcher(bundle.getSymbolicName()).matches()) + { + //get the file location of the jar and put it into the list of container jars that will be scanned for stuff (including tlds) + matchingResources.addAll(getBundleAsResource(bundle)); + } + } + if (names != null) + { + //if there is an explicit bundle name, then check if it matches + if (names.contains(bundle.getSymbolicName())) + matchingResources.addAll(getBundleAsResource(bundle)); + } + } + } + + for (Resource r:matchingResources) + { + context.getMetaData().addContainerJar(r); + } + } + + + + /** + * Consider the fragment bundles associated with the bundle of the webapp being deployed. + * + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#findJars(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + protected List<Resource> findJars (WebAppContext context) + throws Exception + { + List<Resource> mergedResources = new ArrayList<Resource>(); + //get jars from WEB-INF/lib if there are any + List<Resource> webInfJars = super.findJars(context); + if (webInfJars != null) + mergedResources.addAll(webInfJars); + + //add fragment jars as if in WEB-INF/lib of the associated webapp + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles((Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE)); + for (Bundle frag : fragments) + { + File fragFile = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(frag); + mergedResources.add(Resource.newResource(fragFile.toURI())); + } + + return mergedResources; + } + + + /** + * Allow fragments to supply some resources that are added to the baseResource of the webapp. + * + * The resources can be either prepended or appended to the baseResource. + * + * @see org.eclipse.jetty.webapp.WebInfConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext) + */ + @Override + public void configure(WebAppContext context) throws Exception + { + TreeMap<String, Resource> patchResourcesPath = new TreeMap<String, Resource>(); + TreeMap<String, Resource> appendedResourcesPath = new TreeMap<String, Resource>(); + + Bundle bundle = (Bundle)context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE); + if (bundle != null) + { + //TODO anything we need to do to improve PackageAdminServiceTracker? + Bundle[] fragments = PackageAdminServiceTracker.INSTANCE.getFragmentsAndRequiredBundles(bundle); + if (fragments != null && fragments.length != 0) + { + // sorted extra resource base found in the fragments. + // the resources are either overriding the resourcebase found in the + // web-bundle + // or appended. + // amongst each resource we sort them according to the alphabetical + // order + // of the name of the internal folder and the symbolic name of the + // fragment. + // this is useful to make sure that the lookup path of those + // resource base defined by fragments is always the same. + // This natural order could be abused to define the order in which + // the base resources are + // looked up. + for (Bundle frag : fragments) + { + String fragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_FRAGMENT_FOLDER_PATH); + String patchFragFolder = (String) frag.getHeaders().get(OSGiWebappConstants.JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH); + if (fragFolder != null) + { + URL fragUrl = frag.getEntry(fragFolder); + if (fragUrl == null) { throw new IllegalArgumentException("Unable to locate " + fragFolder + + " inside " + + " the fragment '" + + frag.getSymbolicName() + + "'"); } + fragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(fragUrl); + String key = fragFolder.startsWith("/") ? fragFolder.substring(1) : fragFolder; + appendedResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(fragUrl)); + } + if (patchFragFolder != null) + { + URL patchFragUrl = frag.getEntry(patchFragFolder); + if (patchFragUrl == null) + { + throw new IllegalArgumentException("Unable to locate " + patchFragUrl + + " inside fragment '"+frag.getSymbolicName()+ "'"); + } + patchFragUrl = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(patchFragUrl); + String key = patchFragFolder.startsWith("/") ? patchFragFolder.substring(1) : patchFragFolder; + patchResourcesPath.put(key + ";" + frag.getSymbolicName(), Resource.newResource(patchFragUrl)); + } + } + if (!appendedResourcesPath.isEmpty()) + context.setAttribute(WebInfConfiguration.RESOURCE_URLS, new ArrayList<Resource>(appendedResourcesPath.values())); + } + } + + super.configure(context); + + // place the patch resources at the beginning of the contexts's resource base + if (!patchResourcesPath.isEmpty()) + { + Resource[] resources = new Resource[1+patchResourcesPath.size()]; + ResourceCollection mergedResources = new ResourceCollection (patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()])); + System.arraycopy(patchResourcesPath.values().toArray(new Resource[patchResourcesPath.size()]), 0, resources, 0, patchResourcesPath.size()); + resources[resources.length-1] = context.getBaseResource(); + context.setBaseResource(new ResourceCollection(resources)); + } + + } + + + + /** + * Resolves the bundle. Usually that would be a single URL per bundle. But we do some more work if there are jars + * embedded in the bundle. + */ + private List<Resource> getBundleAsResource(Bundle bundle) + throws Exception + { + List<Resource> resources = new ArrayList<Resource>(); + + File file = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bundle); + if (file.isDirectory()) + { + for (File f : file.listFiles()) + { + if (f.getName().endsWith(".jar") && f.isFile()) + { + resources.add(Resource.newResource(f)); + } + else if (f.isDirectory() && f.getName().equals("lib")) + { + for (File f2 : file.listFiles()) + { + if (f2.getName().endsWith(".jar") && f2.isFile()) + { + resources.add(Resource.newResource(f)); + } + } + } + } + resources.add(Resource.newResource(file)); //TODO really??? + } + else + { + resources.add(Resource.newResource(file)); + } + + return resources; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java index 3cb21232ed..1908eb2de5 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java @@ -23,6 +23,20 @@ package org.eclipse.jetty.osgi.boot; */ public class OSGiWebappConstants { + /** service property osgi.web.symbolicname. See OSGi r4 */ + public static final String OSGI_WEB_SYMBOLICNAME = "osgi.web.symbolicname"; + + /** service property osgi.web.symbolicname. See OSGi r4 */ + public static final String OSGI_WEB_VERSION = "osgi.web.version"; + + /** service property osgi.web.contextpath. See OSGi r4 */ + public static final String OSGI_WEB_CONTEXTPATH = "osgi.web.contextpath"; + + /** See OSGi r4 p.427 */ + public static final String OSGI_BUNDLECONTEXT = "osgi-bundlecontext"; + + + /** url scheme to deploy war file as bundled webapp */ public static final String RFC66_WAR_URL_SCHEME = "war"; @@ -59,32 +73,60 @@ public class OSGiWebappConstants * this will override static resources with the same name in the web-bundle. */ public static final String JETTY_WAR_PATCH_FRAGMENT_FOLDER_PATH = "Jetty-WarPatchFragmentFolderPath"; - // OSGi ContextHandler service properties. - /** web app context path */ + + /** + * web app context path + * @deprecated see RFC66_WEB_CONTEXTPATH + */ public static final String SERVICE_PROP_CONTEXT_PATH = "contextPath"; - /** Path to the web application base folder */ + + /** + * Path to the web application base folder + * @deprecated see JETTY_WAR_FOLDER_PATH + */ public static final String SERVICE_PROP_WAR = "war"; - /** Extra classpath */ + /** + * Extra classpath + * @deprecated see JETTY_EXTRA_CLASSPATH + */ public static final String SERVICE_PROP_EXTRA_CLASSPATH = "extraClasspath"; + + public static final String JETTY_EXTRA_CLASSPATH = "Jetty-extraClasspath"; - /** jetty context file path */ + /** + * jetty context file path + * @deprecated see JETTY_CONTEXT_FILE_PATH + */ public static final String SERVICE_PROP_CONTEXT_FILE_PATH = "contextFilePath"; - /** web.xml file path */ + /** + * web.xml file path + * @deprecated see JETTY_WEB_XML_PATH + */ public static final String SERVICE_PROP_WEB_XML_PATH = "webXmlFilePath"; + + public static final String JETTY_WEB_XML_PATH = "Jetty-WebXmlFilePath"; - /** defaultweb.xml file path */ + /** + * defaultweb.xml file path + * @deprecated see JETTY_DEFAULT_WEB_XML_PATH + */ public static final String SERVICE_PROP_DEFAULT_WEB_XML_PATH = "defaultWebXmlFilePath"; + + public static final String JETTY_DEFAULT_WEB_XML_PATH = "Jetty-defaultWebXmlFilePath"; /** * path to the base folder that overrides the computed bundle installation * location if not null useful to install webapps or jetty context files * that are in fact not embedded in a bundle + * @deprecated see JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE */ public static final String SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE = "thisBundleInstall"; + public static final String JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE = "Jetty-bundleInstall"; + /** * Comma separated list of bundles that contain tld file used by the webapp. */ @@ -94,4 +136,14 @@ public class OSGiWebappConstants * Both the name of the manifest header and the name of the service property. */ public static final String SERVICE_PROP_REQUIRE_TLD_BUNDLE = REQUIRE_TLD_BUNDLE; + + public static final String WATERMARK = "o.e.j.o.b.watermark"; + + /** + * Set of extra dirs that must not be served by osgi webapps + */ + public static final String[] DEFAULT_PROTECTED_OSGI_TARGETS = {"/osgi-inf", "/osgi-opts"}; + + + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java new file mode 100644 index 0000000000..b7630cb188 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceContextProvider.java @@ -0,0 +1,186 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + +/** + * ServiceContextProvider + * + * + */ +public class ServiceContextProvider extends AbstractContextProvider implements ServiceProvider +{ + private static final Logger LOG = Log.getLogger(AbstractContextProvider.class); + + private Map<ServiceReference, App> _serviceMap = new HashMap<ServiceReference, App>(); + + private ServiceRegistration _serviceRegForServices; + + + /** + * ServiceApp + * + * + */ + public class ServiceApp extends OSGiApp + { + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String contextFile, String originId) + { + super(manager, provider, bundle, properties, contextFile, originId); + } + + public ServiceApp(DeploymentManager manager, AppProvider provider, String originId, Bundle bundle, String contextFile) + { + super(manager, provider, originId, bundle, contextFile); + } + + @Override + public void registerAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + + @Override + protected void deregisterAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + } + + + + /* ------------------------------------------------------------ */ + public ServiceContextProvider(ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + public boolean serviceAdded (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || serviceRef == null) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that has already been registered as a service by another of our deployers + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + try + { + //See if there is a context file to apply to this pre-made context + String contextFile = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); + if (contextFile == null) + contextFile = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + + String[] keys = serviceRef.getPropertyKeys(); + Dictionary properties = new Hashtable<String, Object>(); + if (keys != null) + { + for (String key:keys) + properties.put(key, serviceRef.getProperty(key)); + } + Bundle bundle = serviceRef.getBundle(); + String originId = bundle.getSymbolicName() + "-" + bundle.getVersion().toString() + "-"+contextFile; + ServiceApp app = new ServiceApp(getDeploymentManager(), this, bundle, properties, contextFile, originId); + app.setHandler(context); //set the pre=made ContextHandler instance + _serviceMap.put(serviceRef, app); + getDeploymentManager().addApp(app); + return true; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + /* ------------------------------------------------------------ */ + public boolean serviceRemoved (ServiceReference serviceRef, ContextHandler context) + { + + if (context == null || serviceRef == null) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that will be deregistered as a service by another of our deployers + + App app = _serviceMap.remove(serviceRef); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + + return false; + } + + + + /* ------------------------------------------------------------ */ + @Override + protected void doStart() throws Exception + { + //register as an osgi service for deploying contexts defined in a bundle, advertising the name of the jetty Server instance we are related to + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + + //register as an osgi service for deploying contexts, advertising the name of the jetty Server instance we are related to + _serviceRegForServices = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ServiceProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForServices != null) + { + try + { + _serviceRegForServices.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + super.doStop(); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java new file mode 100644 index 0000000000..f2304c6b13 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceProvider.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.osgi.framework.ServiceReference; + +public interface ServiceProvider +{ + public boolean serviceAdded (ServiceReference ref, ContextHandler handler) throws Exception; + + public boolean serviceRemoved (ServiceReference ref, ContextHandler handler) throws Exception; +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java new file mode 100644 index 0000000000..e3f97f0913 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/ServiceWebAppProvider.java @@ -0,0 +1,248 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.EventSender; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + + + +/** + * ServiceWebAppProvider + * + * Jetty Provider that knows how to deploy a WebApp that has been registered as an OSGi service. + * + */ +public class ServiceWebAppProvider extends AbstractWebAppProvider implements ServiceProvider +{ + private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class); + + + /** + * Map of ServiceRef to App. Used when it is an osgi service that is a WebAppContext. + */ + private Map<ServiceReference, App> _serviceMap = new HashMap<ServiceReference, App>(); + + private ServiceRegistration _serviceRegForServices; + + + /** + * ServiceApp + * + * + */ + public class ServiceApp extends OSGiApp + { + + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId) + { + super(manager, provider, bundle, properties, originId); + } + + public ServiceApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId) + { + super(manager, provider, bundle, originId); + } + + @Override + public void registerAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + + @Override + protected void deregisterAsOSGiService() throws Exception + { + //not applicable for apps that are already services + } + } + + + + /* ------------------------------------------------------------ */ + /** + * @param wrapper + */ + public ServiceWebAppProvider (ServerInstanceWrapper wrapper) + { + super(wrapper); + } + + + /* ------------------------------------------------------------ */ + /** + * A webapp that was deployed as an osgi service has been added, + * and we want to deploy it. + * + * @param context the webapp + */ + public boolean serviceAdded (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || !(context instanceof WebAppContext)) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a webapp that has already been registered as a service by another of our deployers + + + WebAppContext webApp = (WebAppContext)context; + Dictionary properties = new Hashtable<String,String>(); + + String contextPath = (String)serviceRef.getProperty(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); + if (contextPath == null) + contextPath = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); + if (contextPath == null) + return false; //No context path + + String base = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); + if (base == null) + base = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR); + + if (base == null) + return false; //No webapp base + + String webdefaultXml = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH); + if (webdefaultXml == null) + webdefaultXml = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH); + if (webdefaultXml != null) + properties.put(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH, webdefaultXml); + + String webXml = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_WEB_XML_PATH); + if (webXml == null) + webXml = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH); + if (webXml != null) + properties.put(OSGiWebappConstants.JETTY_WEB_XML_PATH, webXml); + + String extraClassPath = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH); + if (extraClassPath == null) + extraClassPath = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH); + if (extraClassPath != null) + properties.put(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH, extraClassPath); + + String bundleInstallOverride = (String)serviceRef.getProperty(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleInstallOverride == null) + bundleInstallOverride = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE); + if (bundleInstallOverride != null) + properties.put(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE, bundleInstallOverride); + + String requiredTlds = (String)serviceRef.getProperty(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + if (requiredTlds == null) + requiredTlds = (String)serviceRef.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE); + if (requiredTlds != null) + properties.put(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requiredTlds); + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getServerInstanceWrapper().getParentClassLoaderForWebapps()); + try + { + String originId = getOriginId(serviceRef.getBundle(), base); + ServiceApp app = new ServiceApp(getDeploymentManager(), this, serviceRef.getBundle(), properties, originId); + app.setContextPath(contextPath); + app.setWebAppPath(base); + app.setWebAppContext(webApp); //set the pre=made webapp instance + _serviceMap.put(serviceRef, app); + getDeploymentManager().addApp(app); + return true; + } + finally + { + Thread.currentThread().setContextClassLoader(cl); + } + } + + + + /* ------------------------------------------------------------ */ + /** + * @param context the webapp + */ + public boolean serviceRemoved (ServiceReference serviceRef, ContextHandler context) + { + if (context == null || !(context instanceof WebAppContext)) + return false; + + String watermark = (String)serviceRef.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return false; //this service represents a contexthandler that will be deregistered as a service by another of our deployers + + App app = _serviceMap.remove(serviceRef); + if (app != null) + { + getDeploymentManager().removeApp(app); + return true; + } + return false; + } + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() + */ + protected void doStart() throws Exception + { + //register as an osgi service for deploying bundles, advertising the name of the jetty Server instance we are related to + Dictionary<String,String> properties = new Hashtable<String,String>(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, getServerInstanceWrapper().getManagedServerName()); + + //register as an osgi service for deploying contexts (discovered as osgi services), advertising the name of the jetty Server instance we are related to + _serviceRegForServices = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(ServiceProvider.class.getName(), this, properties); + super.doStart(); + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() + */ + @Override + protected void doStop() throws Exception + { + //unregister ourselves + if (_serviceRegForServices != null) + { + try + { + _serviceRegForServices.unregister(); + } + catch (Exception e) + { + LOG.warn(e); + } + } + super.doStop(); + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java index 5624d07e62..ce8a51455f 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java @@ -28,6 +28,7 @@ import java.util.StringTokenizer; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.log.Log; @@ -36,6 +37,9 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; /** + * DefaultJettyAtJettyHomeHelper + * + * * Called by the {@link JettyBootstrapActivator} during the starting of the * bundle. If the system property 'jetty.home' is defined and points to a * folder, then setup the corresponding jetty server. @@ -49,49 +53,20 @@ public class DefaultJettyAtJettyHomeHelper * used to configure jetty. By default the value is 'etc/jetty.xml' when the * path is relative the file is resolved relatively to jettyhome. */ - public static final String SYS_PROP_JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; + public static final String JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; /** - * Usual system property used as the hostname for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_HOME = "jetty.home"; - - /** - * System property to point to a bundle that embeds a jetty configuration - * and that jetty configuration should be the default jetty server. First we - * look for jetty.home. If we don't find it then we look for this property. - */ - public static final String SYS_PROP_JETTY_HOME_BUNDLE = "jetty.home.bundle"; - - /** - * Usual system property used as the hostname for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_HOST = "jetty.host"; - - /** - * Usual system property used as the port for http for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_PORT = "jetty.port"; - - /** - * Usual system property used as the port for https for a typical jetty - * configuration. - */ - public static final String SYS_PROP_JETTY_PORT_SSL = "jetty.port.ssl"; - - /** * Set of config files to apply to a jetty Server instance if none are supplied by SYS_PROP_JETTY_ETC_FILES */ - public static final String DEFAULT_JETTY_ETC_FILES = "jetty.xml,jetty-selector.xml,jetty-deployer.xml"; + public static final String DEFAULT_JETTY_ETC_FILES = "etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-deployer.xml"; /** * Default location within bundle of a jetty home dir. */ public static final String DEFAULT_JETTYHOME = "/jettyhome"; - + + + /* ------------------------------------------------------------ */ /** * Called by the JettyBootStrapActivator. If the system property jetty.home * is defined and points to a folder, creates a corresponding jetty @@ -116,8 +91,8 @@ public class DefaultJettyAtJettyHomeHelper */ public static void startJettyAtJettyHome(BundleContext bundleContext) throws Exception { - String jettyHomeSysProp = System.getProperty(SYS_PROP_JETTY_HOME); - String jettyHomeBundleSysProp = System.getProperty(SYS_PROP_JETTY_HOME_BUNDLE); + String jettyHomeSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME); + String jettyHomeBundleSysProp = System.getProperty(OSGiServerConstants.JETTY_HOME_BUNDLE); File jettyHome = null; Bundle jettyHomeBundle = null; if (jettyHomeSysProp != null) @@ -174,17 +149,18 @@ public class DefaultJettyAtJettyHomeHelper // these properties usually are the ones passed to this type of // configuration. - setProperty(properties, SYS_PROP_JETTY_HOME, System.getProperty(SYS_PROP_JETTY_HOME)); - setProperty(properties, SYS_PROP_JETTY_HOST, System.getProperty(SYS_PROP_JETTY_HOST)); - setProperty(properties, SYS_PROP_JETTY_PORT, System.getProperty(SYS_PROP_JETTY_PORT)); - setProperty(properties, SYS_PROP_JETTY_PORT_SSL, System.getProperty(SYS_PROP_JETTY_PORT_SSL)); + setProperty(properties, OSGiServerConstants.JETTY_HOME, System.getProperty(OSGiServerConstants.JETTY_HOME)); + setProperty(properties, OSGiServerConstants.JETTY_HOST, System.getProperty(OSGiServerConstants.JETTY_HOST)); + setProperty(properties, OSGiServerConstants.JETTY_PORT, System.getProperty(OSGiServerConstants.JETTY_PORT)); + setProperty(properties, OSGiServerConstants.JETTY_PORT_SSL, System.getProperty(OSGiServerConstants.JETTY_PORT_SSL)); //register the Server instance as an OSGi service. bundleContext.registerService(Server.class.getName(), server, properties); - // hookNestedConnectorToBridgeServlet(server); - } - + + + + /* ------------------------------------------------------------ */ /** * Minimum setup for the location of the configuration files given a * jettyhome folder. Reads the system property jetty.etc.config.urls and @@ -196,7 +172,7 @@ public class DefaultJettyAtJettyHomeHelper */ private static String getJettyConfigurationURLs(File jettyhome) { - String jettyetc = System.getProperty(SYS_PROP_JETTY_ETC_FILES, "etc/jetty.xml"); + String jettyetc = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); StringTokenizer tokenizer = new StringTokenizer(jettyetc, ";,", false); StringBuilder res = new StringBuilder(); while (tokenizer.hasMoreTokens()) @@ -218,7 +194,9 @@ public class DefaultJettyAtJettyHomeHelper } return res.toString(); } - + + + /* ------------------------------------------------------------ */ /** * Minimum setup for the location of the configuration files given a * configuration embedded inside a bundle. Reads the system property @@ -230,7 +208,7 @@ public class DefaultJettyAtJettyHomeHelper */ private static String getJettyConfigurationURLs(Bundle configurationBundle) { - String files = System.getProperty(SYS_PROP_JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); + String files = System.getProperty(JETTY_ETC_FILES, DEFAULT_JETTY_ETC_FILES); StringTokenizer tokenizer = new StringTokenizer(files, ";,", false); StringBuilder res = new StringBuilder(); @@ -246,36 +224,39 @@ public class DefaultJettyAtJettyHomeHelper else { //relative file path - Enumeration<URL> enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, etcFile); - + Enumeration<URL> enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, etcFile); + // default for org.eclipse.osgi.boot where we look inside // jettyhome for the default embedded configuration. // default inside jettyhome. this way fragments to the bundle // can define their own configuration. if ((enUrls == null || !enUrls.hasMoreElements())) { - String tmp = DEFAULT_JETTYHOME+"/etc/"+etcFile; - enUrls = BundleFileLocatorHelper.DEFAULT.findEntries(configurationBundle, tmp); - LOG.info("Configuring jetty with the default embedded configuration:" + "bundle: " + String tmp = DEFAULT_JETTYHOME+etcFile; + enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp); + LOG.info("Configuring jetty from bundle: " + configurationBundle.getSymbolicName() - + " config: "+tmp); + + " with "+tmp); } if (enUrls == null || !enUrls.hasMoreElements()) { - LOG.warn("Unable to locate a jetty configuration file for " + etcFile); + throw new IllegalStateException ("Unable to locate a jetty configuration file for " + etcFile); } if (enUrls != null) { while (enUrls.hasMoreElements()) { - appendToCommaSeparatedList(res, enUrls.nextElement().toString()); + URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(enUrls.nextElement()); + appendToCommaSeparatedList(res, url.toString()); } } } } return res.toString(); } - + + + /* ------------------------------------------------------------ */ private static void appendToCommaSeparatedList(StringBuilder buffer, String value) { if (buffer.length() != 0) @@ -284,7 +265,9 @@ public class DefaultJettyAtJettyHomeHelper } buffer.append(value); } - + + + /* ------------------------------------------------------------ */ private static void setProperty(Dictionary<String,String> properties, String key, String value) { if (value != null) @@ -292,7 +275,9 @@ public class DefaultJettyAtJettyHomeHelper properties.put(key, value); } } - + + + /* ------------------------------------------------------------ */ /** * recursively substitute the ${sysprop} by their actual system property. * ${sysprop,defaultvalue} will use 'defaultvalue' as the value if no diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java index 10f442f9c4..f9fb433ac9 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; -import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; @@ -31,16 +30,24 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import org.eclipse.jetty.deploy.AppLifeCycle; import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.deploy.bindings.StandardStarter; +import org.eclipse.jetty.deploy.bindings.StandardStopper; +import org.eclipse.jetty.osgi.boot.BundleContextProvider; +import org.eclipse.jetty.osgi.boot.BundleWebAppProvider; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.osgi.boot.OSGiDeployer; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.OSGiUndeployer; +import org.eclipse.jetty.osgi.boot.ServiceContextProvider; +import org.eclipse.jetty.osgi.boot.ServiceWebAppProvider; import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader; +import org.eclipse.jetty.osgi.boot.internal.webapp.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer; import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.util.IO; @@ -66,6 +73,8 @@ public class ServerInstanceWrapper public static final String PROPERTY_THIS_JETTY_XML_FOLDER_URL = "this.jetty.xml.parent.folder.url"; private static Logger LOG = Log.getLogger(ServerInstanceWrapper.class.getName()); + + private final String _managedServerName; @@ -74,7 +83,7 @@ public class ServerInstanceWrapper */ private Server _server; - private ContextHandlerCollection _ctxtHandler; + private ContextHandlerCollection _ctxtCollection; /** * This is the class loader that should be the parent classloader of any @@ -84,21 +93,22 @@ public class ServerInstanceWrapper private ClassLoader _commonParentClassLoaderForWebapps; private DeploymentManager _deploymentManager; - - private OSGiAppProvider _provider; - - private WebBundleDeployerHelper _webBundleDeployerHelper; - + + + /* ------------------------------------------------------------ */ public ServerInstanceWrapper(String managedServerName) { _managedServerName = managedServerName; } + /* ------------------------------------------------------------ */ public String getManagedServerName() { return _managedServerName; } - + + + /* ------------------------------------------------------------ */ /** * The classloader that should be the parent classloader for each webapp * deployed on this server. @@ -109,7 +119,9 @@ public class ServerInstanceWrapper { return _commonParentClassLoaderForWebapps; } - + + + /* ------------------------------------------------------------ */ /** * @return The deployment manager registered on this server. */ @@ -117,33 +129,28 @@ public class ServerInstanceWrapper { return _deploymentManager; } - + + + /* ------------------------------------------------------------ */ /** * @return The app provider registered on this server. */ - public OSGiAppProvider getOSGiAppProvider() - { - return _provider; - } - public Server getServer() { return _server; } - public WebBundleDeployerHelper getWebBundleDeployerHelp() - { - return _webBundleDeployerHelper; - } - + /* ------------------------------------------------------------ */ /** * @return The collection of context handlers */ public ContextHandlerCollection getContextHandlerCollection() { - return _ctxtHandler; + return _ctxtCollection; } - + + + /* ------------------------------------------------------------ */ public void start(Server server, Dictionary props) throws Exception { _server = server; @@ -158,20 +165,20 @@ public class ServerInstanceWrapper List<File> shared = sharedURLs != null ? extractFiles(sharedURLs) : null; libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader(shared, null, server, JettyBootstrapActivator.class.getClassLoader()); + if (LOG.isDebugEnabled()) LOG.debug("LibExtClassLoader = "+libExtClassLoader); + Thread.currentThread().setContextClassLoader(libExtClassLoader); configure(server, props); init(); - // now that we have an app provider we can call the registration - // customizer. - URL[] jarsWithTlds = getJarsWithTlds(); _commonParentClassLoaderForWebapps = jarsWithTlds == null ? libExtClassLoader : new TldLocatableURLClassloader(libExtClassLoader, jarsWithTlds); + + if (LOG.isDebugEnabled()) LOG.debug("common classloader = "+_commonParentClassLoaderForWebapps); server.start(); - _webBundleDeployerHelper = new WebBundleDeployerHelper(this); } catch (Exception e) { @@ -194,7 +201,7 @@ public class ServerInstanceWrapper } } - + /* ------------------------------------------------------------ */ public void stop() { try @@ -209,7 +216,9 @@ public class ServerInstanceWrapper LOG.warn(e); } } - + + + /* ------------------------------------------------------------ */ /** * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. * Should support a way to plug more bundles that contain taglibs. @@ -233,11 +242,19 @@ public class ServerInstanceWrapper */ private URL[] getJarsWithTlds() throws Exception { + + //Jars that are added onto the equivalent of the container classpath are: + // jstl jars: identified by the class WhenTag (and the boot-bundle manifest imports the jstl packages + // bundles identified by System property org.eclipse.jetty.osgi.tldbundles + // bundle symbolic name patterns defined in the DeploymentManager + // + // Any bundles mentioned in the Require-TldBundle manifest header of the webapp bundle MUST ALSO HAVE Import-Bundle + // in order to get them onto the classpath of the webapp. + ArrayList<URL> res = new ArrayList<URL>(); - WebBundleDeployerHelper.staticInit();// that is not looking great. - for (WebappRegistrationCustomizer regCustomizer : WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS) + for (WebappRegistrationCustomizer regCustomizer : WebBundleTrackerCustomizer.JSP_REGISTRATION_HELPERS) { - URL[] urls = regCustomizer.getJarsWithTlds(_provider, WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER); + URL[] urls = regCustomizer.getJarsWithTlds(_deploymentManager, BundleFileLocatorHelperFactory.getFactory().getHelper()); for (URL url : urls) { if (!res.contains(url)) res.add(url); @@ -248,7 +265,9 @@ public class ServerInstanceWrapper else return null; } - + + + /* ------------------------------------------------------------ */ private void configure(Server server, Dictionary props) throws Exception { String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS); @@ -256,15 +275,20 @@ public class ServerInstanceWrapper if (jettyConfigurations == null || jettyConfigurations.isEmpty()) { return; } Map<String, Object> id_map = new HashMap<String, Object>(); - //TODO need to put in the id of the server being configured + //Put in a mapping for the id "Server" and the name of the server as the instance being configured id_map.put("Server", server); + id_map.put((String)props.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME), server); + Map<String, String> properties = new HashMap<String, String>(); Enumeration<Object> en = props.keys(); while (en.hasMoreElements()) { Object key = en.nextElement(); Object value = props.get(key); - properties.put(String.valueOf(key), String.valueOf(value)); + String keyStr = String.valueOf(key); + String valStr = String.valueOf(value); + properties.put(keyStr, valStr); + server.setAttribute(keyStr, valStr); } for (URL jettyConfiguration : jettyConfigurations) @@ -274,6 +298,11 @@ public class ServerInstanceWrapper { // Execute a Jetty configuration file Resource r = Resource.newResource(jettyConfiguration); + if (!r.exists()) + { + LOG.warn("File does not exist "+r); + continue; + } is = r.getInputStream(); XmlConfiguration config = new XmlConfiguration(is); config.getIdMap().putAll(id_map); @@ -316,45 +345,97 @@ public class ServerInstanceWrapper * * It is assumed the server has already been configured with the ContextHandlerCollection structure. * - * The server must have an instance of OSGiAppProvider. If one is not provided, it is created. */ private void init() { // Get the context handler - _ctxtHandler = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class); + _ctxtCollection = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class); - // get a deployerManager - Collection<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class); + if (_ctxtCollection == null) + throw new IllegalStateException("ERROR: No ContextHandlerCollection configured in Server"); + + List<String> providerClassNames = new ArrayList<String>(); + + // get a deployerManager and some providers + List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class); if (deployers != null && !deployers.isEmpty()) { - _deploymentManager = deployers.iterator().next(); - + _deploymentManager = deployers.get(0); + for (AppProvider provider : _deploymentManager.getAppProviders()) { - if (provider instanceof OSGiAppProvider) - { - _provider = (OSGiAppProvider) provider; - break; - } + providerClassNames.add(provider.getClass().getName()); } - if (_provider == null) + } + else + { + //add some kind of default + _deploymentManager = new DeploymentManager(); + _deploymentManager.setContexts(_ctxtCollection); + _server.addBean(_deploymentManager); + } + + _deploymentManager.setUseStandardBindings(false); + List<AppLifeCycle.Binding> deploymentLifeCycleBindings = new ArrayList<AppLifeCycle.Binding>(); + deploymentLifeCycleBindings.add(new OSGiDeployer()); + deploymentLifeCycleBindings.add(new StandardStarter()); + deploymentLifeCycleBindings.add(new StandardStopper()); + deploymentLifeCycleBindings.add(new OSGiUndeployer()); + _deploymentManager.setLifeCycleBindings(deploymentLifeCycleBindings); + + if (!providerClassNames.contains(BundleWebAppProvider.class.getName())) + { + // create it on the fly with reasonable default values. + try { - // create it on the fly with reasonable default values. - try - { - _provider = new OSGiAppProvider(); - _provider.setMonitoredDirResource(Resource.newResource(getDefaultOSGiContextsHome(new File(System.getProperty("jetty.home"))).toURI())); - } - catch (IOException e) - { - LOG.warn(e); - } - _deploymentManager.addAppProvider(_provider); + BundleWebAppProvider webAppProvider = new BundleWebAppProvider(this); + _deploymentManager.addAppProvider(webAppProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } + + if (!providerClassNames.contains(ServiceWebAppProvider.class.getName())) + { + // create it on the fly with reasonable default values. + try + { + ServiceWebAppProvider webAppProvider = new ServiceWebAppProvider(this); + _deploymentManager.addAppProvider(webAppProvider); + } + catch (Exception e) + { + LOG.warn(e); } } - if (_ctxtHandler == null || _provider == null) throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured"); + if (!providerClassNames.contains(BundleContextProvider.class.getName())) + { + try + { + BundleContextProvider contextProvider = new BundleContextProvider(this); + _deploymentManager.addAppProvider(contextProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } + if (!providerClassNames.contains(ServiceContextProvider.class.getName())) + { + try + { + ServiceContextProvider contextProvider = new ServiceContextProvider(this); + _deploymentManager.addAppProvider(contextProvider); + } + catch (Exception e) + { + LOG.warn(e); + } + } } /** @@ -381,10 +462,6 @@ public class ServerInstanceWrapper return new File(jettyHome, "/contexts"); } - File getOSGiContextsHome() - { - return _provider.getContextXmlDirAsFile(); - } /** * @return the urls in this string. @@ -398,7 +475,7 @@ public class ServerInstanceWrapper String tok = tokenizer.nextToken(); try { - urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok))); + urls.add(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(new URL(tok))); } catch (Throwable mfe) { @@ -422,7 +499,7 @@ public class ServerInstanceWrapper try { URL url = new URL(tok); - url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url); + url = BundleFileLocatorHelperFactory.getFactory().getHelper().getFileURL(url); if (url.getProtocol().equals("file")) { Resource res = Resource.newResource(url); diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java new file mode 100644 index 0000000000..197008d8f0 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/BundleFileLocatorHelperFactory.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * BundleFileLocatorHelperFactory + * + * Obtain a helper for locating files based on the bundle. + */ +public class BundleFileLocatorHelperFactory +{ + private static final Logger LOG = Log.getLogger(BundleFileLocatorHelperFactory.class); + + private static BundleFileLocatorHelperFactory _instance = new BundleFileLocatorHelperFactory(); + + private BundleFileLocatorHelperFactory() {} + + public static BundleFileLocatorHelperFactory getFactory() + { + return _instance; + } + + public BundleFileLocatorHelper getHelper() + { + BundleFileLocatorHelper helper = BundleFileLocatorHelper.DEFAULT; + try + { + //see if a fragment has supplied an alternative + helper = (BundleFileLocatorHelper) Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + LOG.ignore(t); + } + return helper; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java index 502d972783..c324c5f676 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; +import org.eclipse.jetty.deploy.ContextDeployer; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java index 00dea2dfb7..6bd352da2a 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java @@ -20,19 +20,25 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.eclipse.jetty.osgi.boot.BundleWebAppProvider; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.ServiceProvider; import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper; import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry; import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -40,107 +46,83 @@ import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; /** - * When a {@link ContextHandler} service is activated we look into it and if the - * corresponding webapp is actually not configured then we go and register it. - * <p> - * The idea is to always go through this class when we deploy a new webapp on - * jetty. - * </p> - * <p> - * We are exposing each web-application as an OSGi service. This lets us update - * the webapps and stop/start them directly at the OSGi layer. It also give us - * many ways to declare those services: Declarative Services for example. <br/> - * It is a bit different from the way the HttpService works where we would have - * a WebappService and we woud register a webapp onto it. <br/> - * It does not go against RFC-66 nor does it prevent us from supporting the - * WebappContainer semantics. - * </p> + * JettyContextHandlerServiceTracker + * + * When a {@link ContextHandler} is activated as an osgi service we find a jetty deployer + * for it. The ContextHandler could be either a WebAppContext or any other derivative of + * ContextHandler. + * + * ContextHandlers and WebApps can also be deployed into jetty without creating them as + * osgi services. Instead, they can be deployed via manifest headers inside bundles. See + * {@link WebBundleTrackerCustomizer}. */ public class JettyContextHandlerServiceTracker implements ServiceListener { - private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName()); - - /** New style: ability to manage multiple jetty instances */ - private final IManagedJettyServerRegistry _registry; - - /** The context-handler to deactivate indexed by context handler */ - private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>(); - - /** - * The index is the bundle-symbolic-name/path/to/context/file when there is - * such thing - */ - private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>(); - - /** in charge of detecting changes in the osgi contexts home folder. */ - private Scanner _scanner; - + private static Logger LOG = Log.getLogger(JettyContextHandlerServiceTracker.class); + + public static final String FILTER = "(objectclass=" + ServiceProvider.class.getName() + ")"; + + + //track all instances of deployers of webapps as bundles + ServiceTracker _serviceTracker; + + + + /* ------------------------------------------------------------ */ /** * @param registry */ - public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception + public JettyContextHandlerServiceTracker() throws Exception { - _registry = registry; + //track all instances of deployers of webapps + Bundle myBundle = FrameworkUtil.getBundle(this.getClass()); + _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null); + _serviceTracker.open(); } - public void stop() throws Exception - { - if (_scanner != null) - { - _scanner.stop(); - } - // the class that created the server is also in charge of stopping it. - // nothing to stop in the WebappRegistrationHelper - - } + + /* ------------------------------------------------------------ */ /** - * @param contextHome Parent folder where the context files can override the - * context files defined in the web bundles: equivalent to the - * contexts folder in a traditional jetty installation. when - * null, just do nothing. + * @param managedServerName + * @return */ - protected void setupContextHomeScanner(File contextHome) throws IOException + public Map<ServiceReference, ServiceProvider> getDeployers(String managedServerName) { - if (contextHome == null) { return; } - final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath(); - _scanner = new Scanner(); - _scanner.setRecursive(true); - _scanner.setReportExistingFilesOnStartup(false); - _scanner.addListener(new Scanner.DiscreteListener() + if (managedServerName == null) + managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; + + Map<ServiceReference, ServiceProvider> candidates = new HashMap<ServiceReference, ServiceProvider>(); + + ServiceReference[] references = _serviceTracker.getServiceReferences(); + if (references != null) { - public void fileAdded(String filename) throws Exception - { - // adding a file does not create a new app, - // it just reloads it with the new custom file. - // well, if the file does not define a context handler, - // then in fact it does remove it. - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); - } - - public void fileChanged(String filename) throws Exception - { - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); - } - - public void fileRemoved(String filename) throws Exception + for (ServiceReference ref:references) { - // removing a file does not remove the app: - // it just goes back to the default embedded in the bundle. - // well, if there was no default then it does remove it. - reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + String name = (String)ref.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + if (managedServerName.equalsIgnoreCase(name)) + { + ServiceProvider candidate = (ServiceProvider)_serviceTracker.getService(ref); + if (candidate != null) + candidates.put(ref, candidate); + } } - }); - + } + return candidates; } + /* ------------------------------------------------------------ */ /** * Receives notification that a service has had a lifecycle change. * * @param ev The <code>ServiceEvent</code> object. */ + /** + * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent) + */ public void serviceChanged(ServiceEvent ev) { ServiceReference sr = ev.getServiceReference(); @@ -149,16 +131,31 @@ public class JettyContextHandlerServiceTracker implements ServiceListener case ServiceEvent.MODIFIED: case ServiceEvent.UNREGISTERING: { - ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference()); - if (ctxtHandler != null && !ctxtHandler.isStopped()) + BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext(); + ContextHandler contextHandler = (ContextHandler) context.getService(sr); + + //if this was not a service that another of our deployers may have deployed (in which case they will undeploy it) + String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK); + + //Get a jetty deployer targetted to the named server instance, or the default one if not named + //The individual deployer will decide if it can remove the context or not + String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName); + if (candidates != null) { - try + boolean removed = false; + Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator(); + while (!removed && itor.hasNext()) { - getWebBundleDeployerHelp(sr).unregister(ctxtHandler); - } - catch (Exception e) - { - __logger.warn(e); + Entry<ServiceReference, ServiceProvider> e = itor.next(); + try + { + removed = e.getValue().serviceRemoved(sr, contextHandler); + } + catch (Exception x) + { + LOG.warn("Error undeploying service representing jetty context ", x); + } } } } @@ -181,210 +178,34 @@ public class JettyContextHandlerServiceTracker implements ServiceListener // is configured elsewhere. return; } - String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); - if (contextHandler instanceof WebAppContext && contextFilePath == null) - // it could be a web-application that will in fact be configured - // via a context file. - // that case is identified by the fact that the contextFilePath - // is not null - // in that case we must use the register context methods. + String watermark = (String)sr.getProperty(OSGiWebappConstants.WATERMARK); + if (watermark != null && !"".equals(watermark)) + return; //one of our deployers just registered the context as an OSGi service, so we can ignore it + + //Get a jetty deployer targetted to the named server instance, or the default one if not named + String serverName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + Map<ServiceReference, ServiceProvider> candidates = getDeployers(serverName); + if (candidates != null) { - WebAppContext webapp = (WebAppContext) contextHandler; - String contextPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); - if (contextPath == null) - { - contextPath = webapp.getContextPath(); - } - String webXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH); - if (webXmlPath == null) - { - webXmlPath = webapp.getDescriptor(); - } - String defaultWebXmlPath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH); - if (defaultWebXmlPath == null) + boolean added = false; + Iterator<Entry<ServiceReference, ServiceProvider>> itor = candidates.entrySet().iterator(); + while (!added && itor.hasNext()) { - String jettyHome = System.getProperty(DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME); - if (jettyHome != null) + Entry<ServiceReference, ServiceProvider> e = itor.next(); + try { - File etc = new File(jettyHome, "etc"); - if (etc.exists() && etc.isDirectory()) - { - File webDefault = new File(etc, "webdefault.xml"); - if (webDefault.exists()) - defaultWebXmlPath = webDefault.getAbsolutePath(); - else - defaultWebXmlPath = webapp.getDefaultsDescriptor(); - } - else - defaultWebXmlPath = webapp.getDefaultsDescriptor(); + added = e.getValue().serviceAdded(sr, contextHandler); + if (added && LOG.isDebugEnabled()) + LOG.debug("Provider "+e.getValue()+" deployed "+contextHandler); } - } - String war = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WAR); - try - { - IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); - if (deployerHelper == null) + catch (Exception x) { - + LOG.warn("Error deploying service representing jetty context", x); } - else - { - WebAppContext handler = deployerHelper.registerWebapplication(contributor, - war, - contextPath, - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), - webXmlPath, defaultWebXmlPath, webapp); - if (handler != null) - { - registerInIndex(handler, sr); - } - } - } - catch (Throwable e) - { - __logger.warn(e); - } - } - else - { - // consider this just an empty skeleton: - if (contextFilePath == null) { throw new IllegalArgumentException("the property contextFilePath is required"); } - try - { - IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); - if (deployerHelper == null) - { - // more warnings? - } - else - { - if (Boolean.TRUE.toString().equals(sr.getProperty(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE))) - { - contextHandler = null; - } - ContextHandler handler = deployerHelper.registerContext(contributor, - contextFilePath, - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), - (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE), - contextHandler); - if (handler != null) - { - registerInIndex(handler, sr); - } - } - } - catch (Throwable e) - { - __logger.warn(e); } } + break; } - break; - } - } - - private void registerInIndex(ContextHandler handler, ServiceReference sr) - { - _indexByServiceReference.put(sr, handler); - String key = getSymbolicNameAndContextFileKey(sr); - if (key != null) - { - _indexByContextFile.put(key, sr); - } - } - - /** - * Returns the ContextHandler to stop. - * - * @param reg - * @return the ContextHandler to stop. - */ - private ContextHandler unregisterInIndex(ServiceReference sr) - { - ContextHandler handler = _indexByServiceReference.remove(sr); - String key = getSymbolicNameAndContextFileKey(sr); - if (key != null) - { - _indexByContextFile.remove(key); - } - if (handler == null) - { - // a warning? - return null; - } - return handler; - } - - /** - * @param sr - * @return The key for a context file within the osgi contexts home folder. - */ - private String getSymbolicNameAndContextFileKey(ServiceReference sr) - { - String contextFilePath = (String) sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); - if (contextFilePath != null) { return sr.getBundle().getSymbolicName() + "/" + contextFilePath; } - return null; - } - - /** - * Called by the scanner when one of the context files is changed. - * - * @param contextFileFully - */ - public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath) - { - String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath); - if (key == null) { return; } - ServiceReference sr = _indexByContextFile.get(key); - if (sr == null) - { - // nothing to do? - return; - } - serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED, sr)); - } - - /** - * @param canFilename - * @return - */ - private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath) - { - if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath)) - { - // why are we here: this does not look like a child of the osgi - // contexts home. - // warning? - return null; } - return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\', '/'); } - - /** - * @return The server on which this webapp is meant to be deployed - */ - private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) - { - if (_registry == null) { return null; } - if (managedServerName == null) - { - managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; - } - ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName); - // System.err.println("Returning " + managedServerName + " = " + - // wrapper); - return wrapper; - } - - private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr) - { - if (_registry == null) { return null; } - String managedServerName = (String) sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); - ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName); - return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null; - } - } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java index 77b07a719d..5f7e6443d9 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java @@ -34,6 +34,7 @@ import java.util.jar.JarFile; import javax.servlet.http.HttpServlet; import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -86,12 +87,12 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe * @param contributor The bundle that defines this web-application. * @throws IOException */ - public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor, BundleClassLoaderHelper bundleClassLoaderHelper) + public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor) throws IOException { super(parent, context); _contributor = contributor; - _osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor); + _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor); } /** diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java index db976358dd..b8adc65705 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java @@ -19,46 +19,70 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; -import java.net.URL; -import java.util.Dictionary; +import java.util.ArrayList; +import java.util.Collection; -import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.BundleProvider; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.osgi.framework.Bundle; import org.osgi.framework.BundleEvent; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.BundleTracker; import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.osgi.util.tracker.ServiceTracker; /** - * Support bundles that declare the webapp directly through headers in their - * manifest. - * <p> - * Those headers will define a new WebApplication: - * <ul> - * <li>Web-ContextPath</li> - * <li>Jetty-WarFolderPath</li> - * </ul> - * </p> - * <p> - * Those headers will define a new app started via a jetty-context or a list of - * them. ',' column is the separator between the various context files. - * <ul> - * <li>Jetty-ContextFilePath</li> - * </ul> - * </p> - * And generate a jetty WebAppContext or another ContextHandler then registers - * it as service. Kind of simpler than declarative services and their xml files. - * Also avoid having the contributing bundle depend on jetty's package for - * WebApp. + * WebBundleTrackerCustomizer + * + * + * Support bundles that declare a webpp or context directly through headers in their + * manifest. They will be deployed to the default jetty Server instance. + * + * If you wish to deploy a context or webapp to a different jetty Server instance, + * register your context/webapp as an osgi service, and set the property OSGiServerConstants.MANAGED_JETTY_SERVER_NAME + * with the name of the Server instance you wish to depoy to. * * @author hmalphettes */ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { private static final Logger LOG = Log.getLogger(WebBundleTrackerCustomizer.class); + + public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>(); + public static final String FILTER = "(&(objectclass=" + BundleProvider.class.getName() + ")"+ + "("+OSGiServerConstants.MANAGED_JETTY_SERVER_NAME+"="+OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME+"))"; + + private ServiceTracker _serviceTracker; + private BundleTracker _bundleTracker; + + /* ------------------------------------------------------------ */ + /** + * @throws Exception + */ + public WebBundleTrackerCustomizer () + throws Exception + { + Bundle myBundle = FrameworkUtil.getBundle(this.getClass()); + + //track all instances of deployers of webapps/contexts as bundles + _serviceTracker = new ServiceTracker(myBundle.getBundleContext(), FrameworkUtil.createFilter(FILTER),null) { + public Object addingService(ServiceReference reference) { + Object object = super.addingService(reference); + LOG.debug("Deployer registered {}", reference); + openBundleTracker(); + return object; + } + }; + _serviceTracker.open(); + } + + + /* ------------------------------------------------------------ */ /** * A bundle is being added to the <code>BundleTracker</code>. * @@ -83,8 +107,7 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { if (bundle.getState() == Bundle.ACTIVE) { - boolean isWebBundle = register(bundle); - return isWebBundle ? bundle : null; + register(bundle); } else if (bundle.getState() == Bundle.STOPPING) { @@ -98,6 +121,8 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer return null; } + + /* ------------------------------------------------------------ */ /** * A bundle tracked by the <code>BundleTracker</code> has been modified. * @@ -125,6 +150,8 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer } } + + /* ------------------------------------------------------------ */ /** * A bundle tracked by the <code>BundleTracker</code> has been removed. * @@ -143,129 +170,81 @@ public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer unregister(bundle); } + + /* ------------------------------------------------------------ */ /** * @param bundle * @return true if this bundle in indeed a web-bundle. */ private boolean register(Bundle bundle) { - Dictionary<?, ?> dic = bundle.getHeaders(); - String warFolderRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); - if (warFolderRelativePath != null) - { - String contextPath = getWebContextPath(bundle, dic, false); - if (contextPath == null || !contextPath.startsWith("/")) - { - LOG.warn("The manifest header '" + OSGiWebappConstants.JETTY_WAR_FOLDER_PATH - + ": " - + warFolderRelativePath - + "' in the bundle " - + bundle.getSymbolicName() - + " is not valid: there is no Web-ContextPath defined in the manifest."); - return false; - } - // create the corresponding service and publish it in the context of - // the contributor bundle. - try - { - JettyBootstrapActivator.registerWebapplication(bundle, warFolderRelativePath, contextPath); - return true; - } - catch (Throwable e) - { - LOG.warn("Starting the web-bundle " + bundle.getSymbolicName() + " threw an exception.", e); - return true;// maybe it did not work maybe it did. safer to track this bundle. - } - } - else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null) + if (bundle == null) + return false; + + //It might be a bundle that we can deploy to our default jetty server instance + boolean deployed = false; + Object[] deployers = _serviceTracker.getServices(); + if (deployers != null) { - String contextFileRelativePath = (String) dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); - if (contextFileRelativePath == null) - { - // nothing to register here. - return false; - } - // support for multiple webapps in the same bundle: - String[] pathes = contextFileRelativePath.split(",;"); - for (String path : pathes) + int i=0; + while (!deployed && i<deployers.length) { + + BundleProvider p = (BundleProvider)deployers[i]; try { - JettyBootstrapActivator.registerContext(bundle, path.trim()); + deployed = p.bundleAdded(bundle); } - catch (Throwable e) + catch (Exception x) { - LOG.warn(e); + LOG.warn("Error deploying bundle for jetty context", x); } - } - return true; - } - else - { - // support for OSGi-RFC66; disclaimer, no access to the actual - // (draft) of the spec: just a couple of posts on the - // world-wide-web. - URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml"); - if (rfc66Webxml == null && dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) == null) - { - return false;// no webapp in here - } - // this is risky: should we make sure that there is no classes and - // jars directly available - // at the root of the of the bundle: otherwise they are accessible - // through the browser. we should enforce that the whole classpath - // is - // pointing to files and folders inside WEB-INF. We should - // filter-out - // META-INF too - String rfc66ContextPath = getWebContextPath(bundle, dic, rfc66Webxml == null); - try - { - JettyBootstrapActivator.registerWebapplication(bundle, ".", rfc66ContextPath); - return true; - } - catch (Throwable e) - { - LOG.warn(e); - return true;// maybe it did not work maybe it did. safer to track this bundle. + i++; } } + + return deployed; } - private String getWebContextPath(Bundle bundle, Dictionary<?, ?> dic, boolean webinfWebxmlExists) - { - String rfc66ContextPath = (String) dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); - if (rfc66ContextPath == null) + /* ------------------------------------------------------------ */ + /** + * @param bundle + */ + private void unregister(Bundle bundle) + { + Object[] deployers = _serviceTracker.getServices(); + boolean undeployed = false; + if (deployers != null) { - if (!webinfWebxmlExists) { return null; } - // extract from the last token of the bundle's location: - // (really ? - // could consider processing the symbolic name as an alternative - // the location will often reflect the version. - // maybe this is relevant when the file is a war) - String location = bundle.getLocation(); - String toks[] = location.replace('\\', '/').split("/"); - rfc66ContextPath = toks[toks.length - 1]; - // remove .jar, .war etc: - int lastDot = rfc66ContextPath.lastIndexOf('.'); - if (lastDot != -1) + int i=0; + while (!undeployed && i<deployers.length) { - rfc66ContextPath = rfc66ContextPath.substring(0, lastDot); + try + { + undeployed = ((BundleProvider)deployers[i++]).bundleRemoved(bundle); + } + catch (Exception x) + { + LOG.warn("Error undeploying bundle for jetty context", x); + } } } - if (!rfc66ContextPath.startsWith("/")) - { - rfc66ContextPath = "/" + rfc66ContextPath; + } + + public void setAndOpenWebBundleTracker(BundleTracker bundleTracker) { + if(_bundleTracker == null) { + _bundleTracker = bundleTracker; + LOG.debug("Bundle tracker is set"); + openBundleTracker(); } - return rfc66ContextPath; } - private void unregister(Bundle bundle) - { - // nothing to do: when the bundle is stopped, each one of its service - // reference is also stopped and that is what we use to stop the - // corresponding - // webapps registered in that bundle. + private void openBundleTracker() { + if(_bundleTracker != null && _serviceTracker.getServices() != null && + _serviceTracker.getServices().length > 0) { + _bundleTracker.open(); + LOG.debug("Bundle tracker has been opened"); + } } } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java new file mode 100644 index 0000000000..edcfde0f57 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleClassLoaderHelperFactory.java @@ -0,0 +1,61 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * BundleClassLoaderHelperFactory + * + * Get a class loader helper adapted for the particular osgi environment. + */ +public class BundleClassLoaderHelperFactory +{ + private static final Logger LOG = Log.getLogger(BundleClassLoaderHelperFactory.class); + + private static BundleClassLoaderHelperFactory _instance = new BundleClassLoaderHelperFactory(); + + public static BundleClassLoaderHelperFactory getFactory() + { + return _instance; + } + + private BundleClassLoaderHelperFactory() + { + } + + public BundleClassLoaderHelper getHelper() + { + //use the default + BundleClassLoaderHelper helper = BundleClassLoaderHelper.DEFAULT; + try + { + //if a fragment has not provided their own impl + helper = (BundleClassLoaderHelper) Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + LOG.ignore(t); + } + + return helper; + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java index ebba3dbd6e..0809b72906 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java @@ -89,5 +89,31 @@ public interface BundleFileLocatorHelper * @return null or all the entries found for that path. */ public Enumeration<URL> findEntries(Bundle bundle, String entryPath); + + /** + * Only useful for equinox: on felix we get the file:// or jar:// url + * already. Other OSGi implementations have not been tested + * <p> + * Get a URL to the bundle entry that uses a common protocol (i.e. file: + * jar: or http: etc.). + * </p> + * + * @return a URL to the bundle entry that uses a common protocol + */ + public URL getLocalURL(URL url); + + /** + * Only useful for equinox: on felix we get the file:// url already. Other + * OSGi implementations have not been tested + * <p> + * Get a URL to the content of the bundle entry that uses the file: + * protocol. The content of the bundle entry may be downloaded or extracted + * to the local file system in order to create a file: URL. + * + * @return a URL to the content of the bundle entry that uses the file: + * protocol + * </p> + */ + public URL getFileURL(URL url); } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java new file mode 100644 index 0000000000..13703fa416 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/EventSender.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.security.auth.login.FailedLoginException; + +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; + +public class EventSender +{ + //OSGi Event Admin events for webapps + public static final String DEPLOYING_EVENT = "org/osgi/service/web/DEPLOYING"; + public static final String DEPLOYED_EVENT = "org/osgi/service/web/DEPLOYED"; + public static final String UNDEPLOYING_EVENT = "org/osgi/service/web/UNDEPLOYING"; + public static final String UNDEPLOYED_EVENT = "org/osgi/service/web/UNDEPLOYED"; + public static final String FAILED_EVENT = "org/osgi/service/web/FAILED"; + + + private static final EventSender __instance = new EventSender(); + private Bundle _myBundle; + private EventAdmin _eventAdmin; + + private EventSender () + { + _myBundle = FrameworkUtil.getBundle(EventSender.class); + ServiceReference ref = _myBundle.getBundleContext().getServiceReference(EventAdmin.class.getName()); + if (ref != null) + _eventAdmin = (EventAdmin)_myBundle.getBundleContext().getService(ref); + } + + + public static EventSender getInstance() + { + return __instance; + } + + public void send (String topic, Bundle wab, String contextPath) + { + if (topic==null || wab==null || contextPath==null) + return; + + send(topic, wab, contextPath, null); + } + + + public void send (String topic, Bundle wab, String contextPath, Exception ex) + { + if (_eventAdmin == null) + return; + + Dictionary<String,Object> props = new Hashtable<String,Object>(); + props.put("bundle.symbolicName", wab.getSymbolicName()); + props.put("bundle.id", wab.getBundleId()); + props.put("bundle", wab); + props.put("bundle.version", wab.getVersion()); + props.put("context.path", contextPath); + props.put("timestamp", System.currentTimeMillis()); + props.put("extender.bundle", _myBundle); + props.put("extender.bundle.symbolicName", _myBundle.getSymbolicName()); + props.put("extender.bundle.id", _myBundle.getBundleId()); + props.put("extender.bundle.version", _myBundle.getVersion()); + + if (FAILED_EVENT.equalsIgnoreCase(topic) && ex != null) + props.put("exception", ex); + + _eventAdmin.sendEvent(new Event(topic, props)); + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java new file mode 100644 index 0000000000..8850f5e639 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/OSGiClassLoader.java @@ -0,0 +1,221 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.osgi.boot.utils; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.osgi.framework.Bundle; + +/** + * OSGiClassLoader + * + * Class loader that is aware of a bundle. Similar to WebAppClassLoader from Jetty + * and the OSGiWebAppClassLoader, but works without webapps. + */ +public class OSGiClassLoader extends URLClassLoader +{ + private static final Logger LOG = Log.getLogger(OSGiClassLoader.class); + + + private Bundle _bundle; + private ClassLoader _osgiBundleClassLoader; + private boolean _lookInOsgiFirst = true; + private ClassLoader _parent; + + /* ------------------------------------------------------------ */ + public OSGiClassLoader(ClassLoader parent, Bundle bundle) + { + super(new URL[]{}, parent); + _parent = getParent(); + _bundle = bundle; + _osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(_bundle); + } + + + + /* ------------------------------------------------------------ */ + /** + * Get a resource from the classloader + * + * Copied from WebAppClassLoader + */ + public URL getResource(String name) + { + URL url= null; + boolean tried_parent= false; + + + if (_parent!=null && !_lookInOsgiFirst) + { + tried_parent= true; + + if (_parent!=null) + url= _parent.getResource(name); + } + + if (url == null) + { + + url = _osgiBundleClassLoader.getResource(name); + + if (url == null && name.startsWith("/")) + { + if (LOG.isDebugEnabled()) + LOG.debug("HACK leading / off " + name); + + url = _osgiBundleClassLoader.getResource(name.substring(1)); + } + } + + if (url == null && !tried_parent) + { + if (_parent!=null) + url= _parent.getResource(name); + } + + if (url != null) + if (LOG.isDebugEnabled()) + LOG.debug("getResource("+name+")=" + url); + + return url; + } + + /* ------------------------------------------------------------ */ + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException + { + return loadClass(name, false); + } + + /* ------------------------------------------------------------ */ + @Override + protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException + { + Class<?> c = findLoadedClass(name); + ClassNotFoundException ex= null; + boolean tried_parent= false; + + if (c == null && _parent!=null && !_lookInOsgiFirst) + { + tried_parent= true; + try + { + c= _parent.loadClass(name); + if (LOG.isDebugEnabled()) + LOG.debug("loaded " + c); + } + catch (ClassNotFoundException e) + { + ex= e; + } + } + + if (c == null) + { + try + { + c= this.findClass(name); + } + catch (ClassNotFoundException e) + { + ex= e; + } + } + + if (c == null && _parent!=null && !tried_parent) + c = _parent.loadClass(name); + + if (c == null) + throw ex; + + if (resolve) + resolveClass(c); + + if (LOG.isDebugEnabled()) + LOG.debug("loaded " + c+ " from "+c.getClassLoader()); + + return c; + } + + /* ------------------------------------------------------------ */ + @Override + public Enumeration<URL> getResources(String name) throws IOException + { + Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name); + Enumeration<URL> urls = super.getResources(name); + if (_lookInOsgiFirst) + { + return Collections.enumeration(toList(osgiUrls, urls)); + } + else + { + return Collections.enumeration(toList(urls, osgiUrls)); + } + } + + + /* ------------------------------------------------------------ */ + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException + { + try + { + return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name); + } + catch (ClassNotFoundException cne) + { + try + { + return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name); + } + catch (ClassNotFoundException cne2) + { + throw cne; + } + } + } + + + + + + /* ------------------------------------------------------------ */ + /** + * @param e + * @param e2 + * @return + */ + private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2) + { + List<URL> list = new ArrayList<URL>(); + while (e != null && e.hasMoreElements()) + list.add(e.nextElement()); + while (e2 != null && e2.hasMoreElements()) + list.add(e2.nextElement()); + return list; + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java index 951443f062..813bff42fc 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java @@ -20,7 +20,8 @@ package org.eclipse.jetty.osgi.boot.utils; import java.net.URL; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; + /** * Fix various shortcomings with the way jasper parses the tld files. @@ -55,6 +56,6 @@ public interface WebappRegistrationCustomizer * @return array of URLs * @throws Exception */ - URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper fileLocator) throws Exception; + URL[] getJarsWithTlds(DeploymentManager manager, BundleFileLocatorHelper fileLocator) throws Exception; } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java index b1cfcc4897..d60c6fdfd5 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java @@ -33,10 +33,6 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper { private static boolean identifiedOsgiImpl = false; - - private static Class BundleWiringClass = null; - private static Method BundleWiringClass_getClassLoader_method = null; - private static Method BundleClass_adapt_method = null; private static boolean isEquinox = false; @@ -45,34 +41,13 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper private static void init(Bundle bundle) { identifiedOsgiImpl = true; - try { - BundleWiringClass = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring"); - if (BundleWiringClass != null) - { - BundleWiringClass_getClassLoader_method = BundleWiringClass.getDeclaredMethod("getClassLoader", new Class[] {}); - BundleClass_adapt_method = bundle.getClass().getDeclaredMethod("adapt", new Class[] { Class.class }); - BundleClass_adapt_method.setAccessible(true); - return; - } + isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null; } catch (Throwable t) { - //nevermind: an older version of OSGi where BundleWiring is not availble - //t.printStackTrace(); - } - - if (!bundle.getClass().getName().startsWith("org.apache.felix")) - { - try - { - isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null; - } - catch (Throwable t) - { - isEquinox = false; - } + isEquinox = false; } if (!isEquinox) { @@ -95,7 +70,6 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper */ public ClassLoader getBundleClassLoader(Bundle bundle) { - //Older OSGi implementations: String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator"); if (bundleActivator == null) { @@ -119,22 +93,6 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper { init(bundle); } - //This works for OSGi 4.2 and more recent. Aka version 1.6 - //It is using ava reflection to execute: - //(BundleClassLoader) bundle.adapt(BundleWiring.class).getClassLoader() - if (BundleClass_adapt_method != null && BundleWiringClass_getClassLoader_method != null) - { - try - { - Object bundleWiring = BundleClass_adapt_method.invoke(bundle, BundleWiringClass); - return (ClassLoader)BundleWiringClass_getClassLoader_method.invoke(bundleWiring, new Object[] {}); - } - catch (Throwable t) - { - t.printStackTrace(); - return null; - } - } if (isEquinox) { return internalGetEquinoxBundleClassLoader(bundle); @@ -177,16 +135,13 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper private static Field Felix_BundleImpl_m_modules_field; private static Field Felix_ModuleImpl_m_classLoader_field; - - private static Field Felix_BundleImpl_m_revisions_field; - private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle) { // assume felix: try { - // now get the current module from the bundle. + // now get the current module from the bundle. // and return the private field m_classLoader of ModuleImpl if (Felix_BundleImpl_m_modules_field == null) { diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java index 3028aa6c2c..2d31459de1 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java @@ -31,13 +31,13 @@ import java.util.zip.ZipFile; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.util.URIUtil; -import org.eclipse.jetty.util.resource.FileResource; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.FileResource; import org.osgi.framework.Bundle; /** - * From a bundle to its location on the filesystem. - * Often assumes the bundle is not a jar. + * From a bundle to its location on the filesystem. Assumes the bundle is not a + * jar. * * @author hmalphettes */ @@ -182,16 +182,10 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper File file = new File(location.substring("file:".length())); return file; } - else - { - //Resort to introspection on felix: - return getBundleInstallLocationInFelix(bundle); - } } return null; } - - + /** * Locate a file inside a bundle. * @@ -303,7 +297,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper * * @return a URL to the bundle entry that uses a common protocol */ - public static URL getLocalURL(URL url) + public URL getLocalURL(URL url) { if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) { @@ -339,7 +333,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper * protocol * </p> */ - public static URL getFileURL(URL url) + public URL getFileURL(URL url) { if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) { @@ -362,85 +356,4 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper return url; } - // Felix introspection - private static Method Felix_BundleImpl_getArchive_method; - private static Method Felix_BundleArchive_getCurrentRevision_method; - private static Method Felix_BundleRevision_getRevisionRootDir_method; - - private static boolean felixIntroSpectionDone = false; - - /** - * Introspection of the implementation classes of Felix-3.x and Felix-4.x. - * <p> - * See org.apache.felix.framework.cache - * In pseudo code: - * <code> - * File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir(); - * return new File(revRootDir, bundle.jar) if it exists? - * else return revRootDir - * </p> - * @param bundle - * @return The File or null if we failed to find it. - */ - private static File getBundleInstallLocationInFelix(Bundle bundle) - { - if (Felix_BundleImpl_getArchive_method == null) { - if (felixIntroSpectionDone) - { - return null; - } - felixIntroSpectionDone = true; - try - { - Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {}); - Felix_BundleImpl_getArchive_method.setAccessible(true); - Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); - Class bundleArchiveClass = archive.getClass(); - Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {}); - Felix_BundleArchive_getCurrentRevision_method.setAccessible(true); - Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); - Class bundleRevisionClass = revision.getClass(); - Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {}); - Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true); - } - catch (Throwable t) - { - //nevermind? - //t.printStackTrace(); - Felix_BundleImpl_getArchive_method = null; - return null; - } - } - if (Felix_BundleImpl_getArchive_method != null) - { - try - { - Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle); - Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive); - File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision); - //System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath()); - File bundleJar = new File(revRootDir, "bundle.jar"); - if (bundleJar.exists()) - { - //bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision - //when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already - //taken care if that scheme earlier. - return bundleJar; - } - else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists()) - { - //this is a DirectoryRevision - return revRootDir; - } - } - catch (Throwable t) - { - //best effort: nevermind - //t.printStackTrace(); - } - } - return null; - } -// -- end Felix introspection - } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java new file mode 100644 index 0000000000..28cba8b997 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorListener.java @@ -0,0 +1,268 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.nested; + +import java.lang.reflect.Method; + +import javax.servlet.http.HttpServlet; + +import org.eclipse.jetty.nested.NestedConnector; +import org.eclipse.jetty.util.component.AbstractLifeCycle.AbstractLifeCycleListener; +import org.eclipse.jetty.util.component.LifeCycle; +import org.osgi.framework.FrameworkUtil; + +/** + * Listens to the start and stop of the NestedConnector to register and + * unregister the NestedConnector with the BridgeServlet. + * <p> + * All interactions with the BridgeServlet are done via introspection to avoid + * depending on it directly. The BridgeServlet lives in the bootstrap-webapp; + * not inside equinox. + * </p> + */ +public class NestedConnectorListener extends AbstractLifeCycleListener +{ + + /** + * Name of the BridgeServlet class. By default + * org.eclipse.equinox.servletbridge.BridgeServlet + */ + private String bridgeServletClassName = "org.eclipse.equinox.servletbridge.BridgeServlet"; + + /** + * Name of the static method on the BridgeServlet class to register the + * servlet delegate. By default 'registerServletDelegate' + */ + private String registerServletDelegateMethodName = "registerServletDelegate"; + + /** + * Name of the static method on the BridgeServlet class to register the + * servlet delegate. By default 'unregisterServletDelegate' + */ + private String unregisterServletDelegateMethodName = "unregisterServletDelegate"; + + /** + * servlet that wraps this NestedConnector and uses the NestedConnector to + * service the requests. + */ + private NestedConnectorServletDelegate _servletDelegate; + + /** + * The NestedConnector listened to. + */ + private NestedConnector nestedConnector; + + /** + * @param bridgeServletClassName Name of the class that is the + * BridgeServlet. By default + * org.eclipse.equinox.servletbridge.BridgeServlet + */ + public void setBridgeServletClassName(String bridgeServletClassName) + { + this.bridgeServletClassName = bridgeServletClassName; + } + + public String getBridgeServletClassName() + { + return this.bridgeServletClassName; + } + + public String getRegisterServletDelegateMethodName() + { + return this.registerServletDelegateMethodName; + } + + public String getUnregisterServletDelegateMethodName() + { + return this.unregisterServletDelegateMethodName; + } + + /** + * @param registerServletDelegateMethodName Name of the static method on the + * BridgeServlet class to register the servlet delegate. + */ + public void setRegisterServletDelegateMethodName(String registerServletDelegateMethodName) + { + this.registerServletDelegateMethodName = registerServletDelegateMethodName; + } + + /** + * @param unregisterServletDelegateMethodName Name of the static method on + * the BridgeServlet class to unregister the servlet delegate. + */ + public void setUnregisterServletDelegateMethodName(String unregisterServletDelegateMethodName) + { + this.unregisterServletDelegateMethodName = unregisterServletDelegateMethodName; + } + + /** + * @param nestedConnector The NestedConnector that we are listening to here. + */ + public void setNestedConnector(NestedConnector nestedConnector) + { + this.nestedConnector = nestedConnector; + } + + /** + * @return The NestedConnector that we are listening to here. + */ + public NestedConnector getNestedConnector() + { + return this.nestedConnector; + } + + @Override + public void lifeCycleStarted(LifeCycle event) + { + try + { + registerWithBridgeServlet(); + } + catch (Exception e) + { + if (e instanceof RuntimeException) { throw (RuntimeException) e; } + throw new RuntimeException("Unable to register the servlet delegate into the BridgeServlet.", e); + } + } + + @Override + public void lifeCycleStopping(LifeCycle event) + { + try + { + unregisterWithBridgeServlet(); + } + catch (Exception e) + { + if (e instanceof RuntimeException) { throw (RuntimeException) e; } + throw new RuntimeException("Unable to unregister the servlet delegate into the BridgeServlet.", e); + } + } + + /** + * Hook into the BridgeServlet + */ + protected void registerWithBridgeServlet() throws Exception + { + _servletDelegate = new NestedConnectorServletDelegate(getNestedConnector()); + try + { + invokeStaticMethod(getBridgeServletClassName(), getRegisterServletDelegateMethodName(), new Class[] { HttpServlet.class }, _servletDelegate); + } + catch (Throwable t) + { + _servletDelegate.destroy(); + _servletDelegate = null; + if (t instanceof Exception) { throw (Exception) t; } + throw new RuntimeException("Unable to register the servlet delegate into the BridgeServlet.", t); + } + } + + /** + * Unhook into the BridgeServlet + */ + protected void unregisterWithBridgeServlet() throws Exception + { + if (_servletDelegate != null) + { + try + { + invokeStaticMethod(getBridgeServletClassName(), getUnregisterServletDelegateMethodName(), new Class[] { HttpServlet.class }, _servletDelegate); + } + catch (Throwable t) + { + if (t instanceof Exception) { throw (Exception) t; } + throw new RuntimeException("Unable to unregister the servlet delegate from the BridgeServlet.", t); + } + finally + { + _servletDelegate.destroy(); + _servletDelegate = null; + } + } + } + + /** + * + * @param clName + * @param methName + * @param argType + * @throws Exception + */ + private static void invokeStaticMethod(String clName, String methName, Class[] argType, Object... args) throws Exception + { + Method m = getMethod(clName, methName, argType); + m.invoke(null, args); + } + + /** + * + * @param clName Class that belongs to the parent classloader of the OSGi + * framework. + * @param methName Name of the method to find. + * @param argType Argument types of the method to find. + * @throws Exception + */ + private static Method getMethod(String clName, String methName, Class... argType) throws Exception + { + Class bridgeServletClass = FrameworkUtil.class.getClassLoader().loadClass(clName); + return getMethod(bridgeServletClass, methName, argType); + } + + private static Method getMethod(Class cl, String methName, Class... argType) throws Exception + { + Method meth = null; + try + { + meth = cl.getMethod(methName, argType); + return meth; + } + catch (Exception e) + { + for (Method m : cl.getMethods()) + { + if (m.getName().equals(methName) && m.getParameterTypes().length == argType.length) + { + int i = 0; + for (Class p : m.getParameterTypes()) + { + Class ap = argType[i]; + if (p.getName().equals(ap.getName()) && !p.equals(ap)) + { + throw new IllegalStateException("The method \"" + m.toGenericString() + + "\" was found. but the parameter class " + + p.getName() + + " is not the same " + + " inside OSGi classloader (" + + ap.getClassLoader() + + ") and inside the " + + cl.getName() + + " classloader (" + + p.getClassLoader() + + ")." + + " Are the ExtensionBundles correctly defined?"); + + } + } + } + } + throw e; + } + } +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java new file mode 100644 index 0000000000..27b5b7a440 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/nested/NestedConnectorServletDelegate.java @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.nested; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; + +import org.eclipse.jetty.nested.NestedConnector; + +/** + * Wraps a NestedConnector into a servlet that can be plugged into + * BridgeServlet#registerServletDelegate + */ +public class NestedConnectorServletDelegate extends HttpServlet +{ + private static final long serialVersionUID = 1L; + + private final NestedConnector _nestedConnector; + + public NestedConnectorServletDelegate(NestedConnector nestedConnector) + { + _nestedConnector = nestedConnector; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException + { + _nestedConnector.service(req, res); + } + +} |