blob: a8ed768291f1302f01da2740be22523908837550 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.felix.http.samples.whiteboard;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory;
import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
/**
* Abstract class that hides all Jetty Websocket specifics and provides a way for the developer to focus on the actual WebSocket implementation.
* @author paulrutters
*/
public abstract class FelixJettyWebSocketServlet extends JettyWebSocketServlet {
private final AtomicBoolean myFirstInitCall = new AtomicBoolean(true);
private final CountDownLatch myInitBarrier = new CountDownLatch(1);
private ServletContext myProxiedContext;
private ServletContextHandler myServletContextHandler;
@Override
public void init() throws ServletException {
// Init, delaying init call until service method is called...
}
@Override
public void destroy() {
// only call destroy when the servlet has been initialized
if (!myFirstInitCall.get()) {
// This is required because WebSocketServlet needs to have it's destroy() method called as well
// Causes NPE otherwise when calling an WS endpoint
super.destroy();
}
}
// This is a workaround required for WebSockets to work in Jetty12, see
// https://www.eclipse.org/forums/index.php/t/1110140/
@Override
public synchronized ServletContext getServletContext() {
if (myProxiedContext == null) {
myProxiedContext = (ServletContext) Proxy.newProxyInstance(JettyWebSocketServlet.class.getClassLoader(),
new Class[]{ServletContext.class}, (proxy, method, methodArgs) -> {
final ServletContext osgiServletContext = super.getServletContext();
if (!"getAttribute".equals(method.getName())) {
return method.invoke(osgiServletContext, methodArgs);
}
final String name = (String) methodArgs[0];
Object value = osgiServletContext.getAttribute(name);
if (value == null && myProxiedContext != null) {
final ServletContext jettyServletContext = myServletContextHandler.getServletContext();
value = jettyServletContext.getAttribute(name);
}
return value;
});
}
return myProxiedContext;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (myFirstInitCall.compareAndSet(true, false)) {
try {
delayedInit();
} catch (Exception e) {
System.err.println("Error delayed init: " + e.getMessage());
} finally {
myInitBarrier.countDown();
}
} else {
try {
myInitBarrier.await();
} catch (final InterruptedException e) {
throw new ServletException("Timed out waiting for initialisation", e);
}
}
// Call JettyWebSocketServlet service method to handle upgrade requests
super.service(req, resp);
}
private void delayedInit() throws ServletException {
// Make sure WebSockets are enabled in Jetty12
ensureWebSocketsInitialized();
// Overide the TCCL so that the internal factory can be found
// Jetty tries to use ServiceLoader, and their fallback is to
// use TCCL, it would be better if we could provide a loader...
final Thread currentThread = Thread.currentThread();
final ClassLoader tccl = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(JettyWebSocketServlet.class.getClassLoader());
try {
super.init();
} finally {
currentThread.setContextClassLoader(tccl);
}
}
private void ensureWebSocketsInitialized() {
final ServletContext osgiServletContext = getServletContext();
myServletContextHandler = ServletContextHandler.getServletContextHandler(osgiServletContext, "WebSockets");
final JettyWebSocketServerContainer serverContainer = JettyWebSocketServerContainer
.getContainer(osgiServletContext);
if (serverContainer == null) {
// Ensure WebSocket components are initialized in Jetty12
final ServletContext jettyServletContext = myServletContextHandler.getServletContext();
WebSocketServerComponents.ensureWebSocketComponents(myServletContextHandler.getServer(),
myServletContextHandler);
WebSocketUpgradeFilter.ensureFilter(jettyServletContext);
WebSocketMappings.ensureMappings(myServletContextHandler);
JettyServerFrameHandlerFactory.getFactory(jettyServletContext);
JettyWebSocketServerContainer.ensureContainer(jettyServletContext);
}
}
}