blob: 98c64d05441ee3a58d64f2215c24a0cc11211a61 [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.wicket.protocol.ws.javax;
import java.io.EOFException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import jakarta.websocket.CloseReason;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.Session;
import org.apache.wicket.Application;
import org.apache.wicket.IApplicationListener;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.util.lang.Checks;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JSR 356 WebSocket Endpoint that integrates with Wicket Native WebSocket's IWebSocketProcessor
*/
public class WicketEndpoint extends Endpoint
{
private static final Logger LOG = LoggerFactory.getLogger(WicketEndpoint.class);
/**
* The name of the request parameter that holds the application name
*/
private static final String WICKET_APP_PARAM_NAME = "wicket-app-name";
private final AtomicBoolean applicationDestroyed = new AtomicBoolean(false);
private JavaxWebSocketProcessor javaxWebSocketProcessor;
@Override
public void onOpen(Session session, EndpointConfig endpointConfig)
{
String appName = getApplicationName(session);
WebApplication app = (WebApplication) WebApplication.get(appName);
app.getApplicationListeners().add(new ApplicationListener(applicationDestroyed));
try
{
ThreadContext.setApplication(app);
javaxWebSocketProcessor = new JavaxWebSocketProcessor(session, app, endpointConfig);
}
finally
{
ThreadContext.detach();
}
}
@Override
public void onClose(Session session, CloseReason closeReason)
{
super.onClose(session, closeReason);
final int closeCode = closeReason.getCloseCode().getCode();
final String reasonPhrase = closeReason.getReasonPhrase();
LOG.debug("Web Socket connection with id '{}' has been closed with code '{}' and reason: {}",
session.getId(), closeCode, reasonPhrase);
if (isApplicationAlive() && javaxWebSocketProcessor != null)
{
javaxWebSocketProcessor.onClose(closeCode, reasonPhrase);
}
}
@Override
public void onError(Session session, Throwable t)
{
if (isIgnorableError(t))
{
LOG.debug("An error occurred in web socket connection with id : {}", session.getId(), t);
}
else
{
LOG.error("An error occurred in web socket connection with id : {}", session.getId(), t);
}
super.onError(session, t);
if (isApplicationAlive() && javaxWebSocketProcessor != null)
{
javaxWebSocketProcessor.onError(t);
}
}
private boolean isIgnorableError(Throwable t)
{
return
t instanceof EOFException ||
(t instanceof IOException && "Broken pipe".equals(t.getMessage()));
}
private boolean isApplicationAlive() {
return applicationDestroyed.get() == false;
}
private String getApplicationName(Session session)
{
String appName = null;
@SuppressWarnings("unchecked")
Map<String, List<String>> parameters = session.getRequestParameterMap();
if (parameters != null)
{
appName = parameters.get(WICKET_APP_PARAM_NAME).get(0);
}
else
{
// Glassfish 4 has null parameters map and non-null query string ...
String queryString = session.getQueryString();
if (!Strings.isEmpty(queryString))
{
String[] params = Strings.split(queryString, '&');
for (String paramPair : params)
{
String[] nameValues = Strings.split(paramPair, '=');
if (WICKET_APP_PARAM_NAME.equals(nameValues[0]))
{
appName = nameValues[1];
}
}
}
}
Checks.notNull(appName, "The application name cannot be read from the upgrade request's parameters");
return appName;
}
private static class ApplicationListener implements IApplicationListener
{
private final AtomicBoolean applicationDestroyed;
private ApplicationListener(AtomicBoolean applicationDestroyed)
{
this.applicationDestroyed = applicationDestroyed;
}
@Override
public void onBeforeDestroyed(Application application)
{
applicationDestroyed.set(true);
}
}
}