Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSR356 Server OSGI Bundle-Activation #3543

Closed
s-b-u opened this issue Apr 10, 2019 · 16 comments
Closed

JSR356 Server OSGI Bundle-Activation #3543

s-b-u opened this issue Apr 10, 2019 · 16 comments
Assignees
Labels
Stale For auto-closed stale issues and pull requests

Comments

@s-b-u
Copy link

s-b-u commented Apr 10, 2019

Because Apache Aries SPI Fly processes provider bundles after activation, the Websocket Endpoint registration fails for a newly created ServerContainer ( WebSocketServerContainerInitializer.configureContext ) within Equinox and Jetty 9.4.15.
If the Bundle-ActivationPolicy could be set to lazy within "org.eclipse.jetty.websocket.javax.websocket.server" then the serviceloader providers (especially javax.websocket.server.ServerEndpointConfig$Configurator ) will be picked up beforehand and the registration works as expected.

@s-b-u s-b-u changed the title OSGI JSR356 Sevver JSR356 Server OSGI Bundle-Activation Apr 10, 2019
@jmcc0nn3ll
Copy link
Contributor

Please add additional information (and what the actual issue or feature request is) and reopen if needed!

@s-b-u
Copy link
Author

s-b-u commented Apr 10, 2019

Please add additional information (and what the actual issue or feature request is) and reopen if needed!

Sorry, I submitted the issue inadvertently to early because of a typing mistake. Now I can not re-open it.

@jmcc0nn3ll jmcc0nn3ll reopened this Apr 10, 2019
@joakime
Copy link
Contributor

joakime commented Apr 10, 2019

@s-b-u interestingly the testcases for this (using paxexam) shows this as working.

@s-b-u
Copy link
Author

s-b-u commented Apr 10, 2019

I should mention, that the Jetty-Server is bootstrapped by the bundle "org.eclipse.equinox.http.jetty" instead of "org.eclipse.jetty.osgi.boot" like in the paxexam tests cases.

@s-b-u
Copy link
Author

s-b-u commented Apr 11, 2019

Here is some code to illustrate the issue

@Component
public class Activator extends Endpoint {

	private void addEndpoint(ServerContainer container) throws DeploymentException {
		container.addEndpoint(ServerEndpointConfig.Builder.create(Activator.class, "/").build());
	}

	@Activate
	protected void activate(BundleContext ctx) throws Exception {

		Server server = new Server(8080);
		ServletContextHandler context = new ServletContextHandler();
		ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);

		Bundle bundle = FrameworkUtil.getBundle(container.getClass());
		System.out.println(bundle.getState());
// 		prints 4=RESOLVED
//		prints 32=ACTIVE when Bundle-ActivationPolicy: lazy or startlevel=3 (Default=4) o.e.j.w.javax.websocket.server
		try {
			addEndpoint(container);
		} catch (RuntimeException e) {
//			in the case o.e.j.w.javax.websocket.server is still in RESOLVED State			
			e.printStackTrace();
//			java.lang.RuntimeException: Cannot load platform configurator at
//			  javax.websocket.server.ServerEndpointConfig$Configurator.fetchContainerDefaultConfigurator(ServerEndpointConfig.java:123)
//			@see  org.apache.aries.spifly.BaseActivator#start() / providerBundleTracker stateMask=Bundle.ACTIVE
			bundle.start();
			addEndpoint(container);
		}

		server.setHandler(context);
		server.start();

	}

	@Override
	public void onOpen(Session session, EndpointConfig config) {

	}
}

@janbartel janbartel self-assigned this Apr 17, 2019
@janbartel
Copy link
Contributor

@s-b-u how are you working around the fact that the javax.websocket-api bundle doesn't contain the correct osgi headers for being able to use the ServiceLoader?

Also, can you spell out exactly how changing to lazy activation for the websocket server bundle works in your environment? I'm still struggling to see the difference with our test environment - take a look at the test case here for how we test it in paxexam: https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootWithJavaxWebSocket.java

@s-b-u
Copy link
Author

s-b-u commented Apr 17, 2019

@janbartel, I use the same approche like within the test, with a fragment.
I think the difference is on line 140, where the o.e.j.w.javax.websocket.server bundle is started explicitly, which has the same effect as decreasing the startlevel on equinox.
That's why I was asking for the lazy Bundle-ActivationPolicy. It seams to be sufficient to take care, that everything is running (serviceloader providers are registered) after the context is configured.

@janbartel
Copy link
Contributor

@s-b-u I've been playing around with this in our test setup. I don't think I fully understand what is happening. What I see is that when the websocket.server bundle is started (even with lazy activation), the ServletContainerInitializers are registered as providers by spifly:

INFO: Examining bundle for SPI provider: org.eclipse.jetty.websocket.server
INFO: Found SPI resource: bundleentry://41.fwk723474040/META-INF/services/javax.servlet.ServletContainerInitializer
INFO: Loaded SPI provider: class org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer
INFO: Registered service: {javax.servlet.ServletContainerInitializer}={serviceloader.mediator=19, service.id=36, service.bundleid=41, service.scope=bundle, .org.apache.aries.spifly.provider.implclass=org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer}
INFO: Registered provider: javax.servlet.ServletContainerInitializer in bundle org.eclipse.jetty.websocket.server
INFO: Found SPI resource: bundleentry://45.fwk723474040/META-INF/services/javax.websocket.server.ServerEndpointConfig$Configurator
INFO: Loaded SPI provider: class org.eclipse.jetty.websocket.jsr356.server.ContainerDefaultConfigurator
INFO: Registered service: {javax.websocket.server.ServerEndpointConfig$Configurator}={serviceloader.mediator=19, service.id=37, service.bundleid=45, service.scope=bundle, .org.apache.aries.spifly.provider.implclass=org.eclipse.jetty.websocket.jsr356.server.ContainerDefaultConfigurator}
INFO: Registered provider: javax.websocket.server.ServerEndpointConfig$Configurator in bundle org.eclipse.jetty.websocket.javax.websocket.server
INFO: Found SPI resource: bundleentry://45.fwk723474040/META-INF/services/javax.servlet.ServletContainerInitializer
INFO: Loaded SPI provider: class org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
INFO: Registered service: {javax.servlet.ServletContainerInitializer}={serviceloader.mediator=19, service.id=38, service.bundleid=45, service.scope=bundle, .org.apache.aries.spifly.provider.implclass=org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer}
INFO: Registered provider: javax.servlet.ServletContainerInitializer in bundle org.eclipse.jetty.websocket.javax.websocket.server

Then I see that jetty finds and runs these SCIs during startup:

org.eclipse.jetty.util[org.eclipse.jetty.annotations.AnnotationConfiguration] : ServletContainerInitializer: 1 org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer
org.eclipse.jetty.util[org.eclipse.jetty.annotations.AnnotationConfiguration] : ServletContainerInitializer: 2 org.eclipse.jetty.apache.jsp.JettyJasperInitializer
org.eclipse.jetty.util[org.eclipse.jetty.annotations.AnnotationConfiguration] : ServletContainerInitializer: 3 org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer

So I'm not sure that it's true that providers are processed after activation?

Moreover, our test works (with the explict websocket server start() call commented out), whether or not the websocket server bundle is lazy activation or not.

So something is going on in your environment that I don't fully understand. I'm not opposed to make the change to lazy activation, but I don't want to do it unless I fully understand the reason, and am sure that it won't cause problems in other use cases.

@s-b-u
Copy link
Author

s-b-u commented Apr 18, 2019

@janbartel I can only refer to The SPI Fly sources to verify the statement.
I put together a little test case so the issue could be reproduced easily.

package org.eclipse.jetty.osgi.embedded.test;

import static org.junit.Assert.assertNotNull;
import static org.ops4j.pax.exam.CoreOptions.bundleStartLevel;
import static org.ops4j.pax.exam.CoreOptions.junitBundles;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.streamBundle;
import static org.ops4j.pax.tinybundles.core.TinyBundles.withBnd;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.tinybundles.core.TinyBundle;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;

import aQute.bnd.osgi.Constants;

@RunWith(PaxExam.class)
public class TestJettyOSGiEmbeddedWithJavaxWebSocket {

	public static class DummyEnpoint extends Endpoint {
		@Override
		public void onOpen(Session session, EndpointConfig config) {
		}
	}

	public static class TestServer implements BundleActivator {

		@Override
		public void start(BundleContext bc) throws Exception {
			try {
				Server server = new Server(8080);
				ServletContextHandler sc = new ServletContextHandler();
				ServerContainer container = WebSocketServerContainerInitializer.configureContext(sc);
				System.out.println(String.format("---------%s----------", FrameworkUtil.getBundle(container.getClass()).getState()));
				
				container.addEndpoint(ServerEndpointConfig.Builder.create(DummyEnpoint.class, "/").build());
				server.setHandler(sc);
				server.start();
				ServletContext servletContext = sc.getServletContext();
				bc.registerService(ServletContext.class, servletContext, new Hashtable<>());
			} catch (Exception e) {
				e.printStackTrace();
				throw e;
			}

		}

		@Override
		public void stop(BundleContext context) throws Exception {

		}

	}

	@Inject
	BundleContext bundleContext = null;

	@Configuration
	public Option[] config() {
		TinyBundle patch = TinyBundles.bundle().set(Constants.FRAGMENT_HOST, "javax.websocket-api")
				.set(Constants.REQUIRE_CAPABILITY,
						"osgi.extender; filter:=\"(osgi.extender=osgi.serviceloader.processor)\"")
				.set(Constants.BUNDLE_SYMBOLICNAME, "javax.websocket.api.fragment");

		TinyBundle testserver = TinyBundles.bundle().add(TestServer.class).add(DummyEnpoint.class)
				.set(Constants.BUNDLE_ACTIVATOR, TestServer.class.getName())
				.set(Constants.BUNDLE_SYMBOLICNAME, "org.eclipse.jetty.osgi.embedded.test")
				.set(Constants.EXPORT_PACKAGE, "org.eclipse.jetty.osgi.embedded.test");

		List<Option> res = new ArrayList<>();
		res.add(bundleStartLevel(4));
		res.add(junitBundles());

		res.add(streamBundle(patch.build()).noStart());

		res.add(mavenBundle("org.ow2.asm", "asm").versionAsInProject());
		res.add(mavenBundle("org.ow2.asm", "asm-commons").versionAsInProject());
		res.add(mavenBundle("org.ow2.asm", "asm-tree").versionAsInProject());
		res.add(mavenBundle("org.apache.aries.spifly", "org.apache.aries.spifly.dynamic.bundle").versionAsInProject()
				.startLevel(1));
		res.add(mavenBundle("org.eclipse.jetty.toolchain", "jetty-osgi-servlet-api").versionAsInProject());
		res.add(mavenBundle("javax.annotation", "javax.annotation-api").versionAsInProject());
		res.add(mavenBundle("org.apache.geronimo.specs", "geronimo-jta_1.1_spec").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-util").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-deploy").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-server").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-servlet").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-http").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-xml").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-webapp").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-io").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-continuation").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-security").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-client").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-jndi").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-plus").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty", "jetty-annotations").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "websocket-api").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "websocket-common").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "websocket-servlet").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "websocket-server").versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "websocket-client").versionAsInProject());		
		res.add(mavenBundle("org.eclipse.jetty.websocket", "javax-websocket-client-impl").versionAsInProject());
		
		// change the order and startlevel of the following bundles to see the effect
		res.add(streamBundle(testserver.build(withBnd())).startLevel(4));		
		res.add(mavenBundle("javax.websocket", "javax.websocket-api").startLevel(4).versionAsInProject());
		res.add(mavenBundle("org.eclipse.jetty.websocket", "javax-websocket-server-impl").startLevel(3).versionAsInProject());
		
		return res.toArray(new Option[res.size()]);
	}

	@Test
	public void test() {

		ServiceReference<ServletContext> serviceReference = bundleContext.getServiceReference(ServletContext.class);
		ServletContext service = bundleContext.getService(serviceReference);
		assertNotNull(service);
	}
}

@janbartel
Copy link
Contributor

@s-b-u thank you for the test case! I'm still struggling to see the difference between the jetty test case (TestJettyOSGiBootWithJavaxWebSocket) where it works without lazy activation, and your test case. Sure, you aren't using jetty's ability to use ServletContainerInitializers to init the websocket, but rather doing it explicitly in code, but I can't quite see why that is making a difference ....

@s-b-u
Copy link
Author

s-b-u commented Apr 23, 2019

Hi @janbartel, even with the jetty test case, the bundle javax-websocket-server-impl bundle has to be started before jetty-osgi-boot and the web-app-bundle arrive the stage, but this is caused by the ServiceLoader approach and no ServiceContainer will be attached to the ServletContext otherwise. The issue I want to address, is that even if I have an already initialized ServiceContainer available, it still can't be used depending on its own load-order, which I hadn't expected.

@stale
Copy link

stale bot commented Apr 23, 2020

This issue has been automatically marked as stale because it has been a full year without activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the Stale For auto-closed stale issues and pull requests label Apr 23, 2020
@stale
Copy link

stale bot commented May 23, 2020

This issue has been closed due to it having no activity.

@stale stale bot closed this as completed May 23, 2020
@janbartel janbartel reopened this May 25, 2020
@stale stale bot removed the Stale For auto-closed stale issues and pull requests label May 25, 2020
@janbartel
Copy link
Contributor

We might be doing some work on the websocket stuff for osgi with #4797 so maybe we can take a look at this one too.

@stale
Copy link

stale bot commented Jun 2, 2021

This issue has been automatically marked as stale because it has been a full year without activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the Stale For auto-closed stale issues and pull requests label Jun 2, 2021
@stale
Copy link

stale bot commented Jul 21, 2021

This issue has been closed due to it having no activity.

@stale stale bot closed this as completed Jul 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Stale For auto-closed stale issues and pull requests
Projects
None yet
Development

No branches or pull requests

4 participants