The following 2 bundles must be deployed to apache felix for the sample jar to work:
org.apache.felix.http.jetty 4.0.6 org.apache.felix.http.servlet-api 1.1.2
org.apache.felix.framework 5.6.10 org.apache.felix.http.jetty 4.0.6 org.apache.felix.http.servlet-api 1.1.2
First let's create a simple annotated web socket: EchoWebSocket.java
package com.example; import java.util.Date; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @WebSocket @SuppressWarnings("unused") public class EchoWebSocket { @OnWebSocketConnect public void onConnect(Session session) { System.out.println("new socket"); } @OnWebSocketClose public void onClose(Session session, int statusCode, String reason) { System.out.println("socked closed [" + statusCode + "] " + reason); } @OnWebSocketMessage public void onText(Session session, String message) { System.out.println("message received: " + message); if (session.isOpen()) { session.getRemote().sendString(message + " " + new Date(), null); } } }Nothing special going on here, just a simple class that prints to the console and responds to messages.
Extend Jetty's WebSocketServlet class to create web sockets. The implementation is trivial: MyWebSocketServlet.java
package com.example; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; public class MyWebSocketServlet extends WebSocketServlet { @Override public void configure(WebSocketServletFactory factory) { factory.register(EchoWebSocket.class); } }The last class we need is an osgi BundleActivator. This class is a little trickier.
Hashtable<String, String> properties = new Hashtable<>(); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/socket"); service = context.registerService(Servlet.class, new MyWebSocketServlet(), properties);but you will run into class loading issues:
Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.websocket.server.WebSocketServerFactory at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at org.eclipse.jetty.websocket.servlet.WebSocketServletFactory$Loader.load(WebSocketServletFactory.java:47) ... 35 moreYou can get around this problem by getting the class loader of the jetty bundle and setting it as the context class loader before registering the Servlet. This ensures the relevant classes are available when the servlet is initialized. It is not a clean solution, but it works.
You can look up the jetty bundle using it's symbolic name: Activator.java [37:44]
private Bundle getJettyBundle(BundleContext context) { for (Bundle bundle : context.getBundles()) { if (bundle.getSymbolicName().equals("org.apache.felix.http.jetty")) { return bundle; } } throw new RuntimeException("Jetty not found"); }Next, we adapt the Bundle to the BundleWiring interface to get the ClassLoader: Activator.java [21:21]
ClassLoader jettyClassLoader = getJettyBundle(context).adapt(BundleWiring.class).getClassLoader();Finally, we assign it as the context class loader: Activator.java [22:22]
Thread.currentThread().setContextClassLoader(jettyClassLoader);Here is the full class: Activator.java
package com.example; import java.util.Hashtable; import javax.servlet.Servlet; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; public class Activator implements BundleActivator { private ServiceRegistration<Servlet> service; @Override public void start(BundleContext context) throws Exception { ClassLoader original = Thread.currentThread().getContextClassLoader(); ClassLoader jettyClassLoader = getJettyBundle(context).adapt(BundleWiring.class).getClassLoader(); Thread.currentThread().setContextClassLoader(jettyClassLoader); try { Hashtable<String, String> properties = new Hashtable<>(); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/socket"); service = context.registerService(Servlet.class, new MyWebSocketServlet(), properties); } finally { Thread.currentThread().setContextClassLoader(original); } } @Override public void stop(BundleContext context) throws Exception { service.unregister(); } private Bundle getJettyBundle(BundleContext context) { for (Bundle bundle : context.getBundles()) { if (bundle.getSymbolicName().equals("org.apache.felix.http.jetty")) { return bundle; } } throw new RuntimeException("Jetty not found"); } }Compile the classes and create a jar with a valid osgi manifest:
Manifest-Version: 1.0 Bundle-Name: example-hello Bundle-SymbolicName: com.example.hello Bundle-Version: 1.0.0 Bundle-Activator: com.eriklievaart.tiqqer.hello.Activator Import-Package: org.osgi.framework,org.osgi.framework.wiring,org.osgi. service.http.whiteboard,javax.servlet,org.eclipse.jetty.websocket.api ,org.eclipse.jetty.websocket.api.annotations,org.eclipse.jetty.websoc ket.servletOnce you have deployed the bundle, you can test it with the web socket tester from my other article: Web Socket Tester On default settings it is available on
ws://localhost:8080/socketThe port of the OSGI HTTP server can be changed using the following config property:
org.osgi.service.http.port=[port]