/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.tyrus.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.CloseReason;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import org.glassfish.tyrus.core.cluster.BroadcastListener;
import org.glassfish.tyrus.core.cluster.ClusterContext;
import org.glassfish.tyrus.core.cluster.ClusterSession;
import org.glassfish.tyrus.core.coder.CoderWrapper;
import org.glassfish.tyrus.core.coder.InputStreamDecoder;
import org.glassfish.tyrus.core.coder.NoOpByteArrayCoder;
import org.glassfish.tyrus.core.coder.NoOpByteBufferCoder;
import org.glassfish.tyrus.core.coder.NoOpTextCoder;
import org.glassfish.tyrus.core.coder.PrimitiveDecoders;
import org.glassfish.tyrus.core.coder.ReaderDecoder;
import org.glassfish.tyrus.core.coder.ToStringEncoder;
import org.glassfish.tyrus.core.frame.BinaryFrame;
import org.glassfish.tyrus.core.frame.Frame;
import org.glassfish.tyrus.core.frame.TextFrame;
import org.glassfish.tyrus.core.l10n.LocalizationMessages;
import org.glassfish.tyrus.core.monitoring.EndpointEventListener;
import org.glassfish.tyrus.spi.UpgradeRequest;
import org.glassfish.tyrus.spi.UpgradeResponse;
/**
* Wraps the registered application class.
* There is one {@link TyrusEndpointWrapper} for each application class, which handles all the methods.
*
* @author Danny Coward (danny.coward at oracle.com)
* @author Stepan Kopriva (stepan.kopriva at oracle.com)
* @author Martin Matula (martin.matula at oracle.com)
* @author Pavel Bucek (pavel.bucek at oracle.com)
*/
public class TyrusEndpointWrapper {
private final static Logger LOGGER = Logger.getLogger(TyrusEndpointWrapper.class.getName());
/**
* The container for this session.
*/
private final WebSocketContainer container;
private final String contextPath;
private final String endpointPath;
private final String serverEndpointPath;
private final List<CoderWrapper<Decoder>> decoders = new ArrayList<CoderWrapper<Decoder>>();
private final List<CoderWrapper<Encoder>> encoders = new ArrayList<CoderWrapper<Encoder>>();
private final EndpointConfig configuration;
private final Class<? extends Endpoint> endpointClass;
private final Endpoint endpoint;
private final Map<TyrusWebSocket, TyrusSession> webSocketToSession =
new ConcurrentHashMap<TyrusWebSocket, TyrusSession>();
private final Map<String, ClusterSession> clusteredSessions =
new ConcurrentHashMap<String, ClusterSession>();
private final ComponentProviderService componentProvider;
private final ServerEndpointConfig.Configurator configurator;
private final Method onOpen;
private final Method onClose;
private final Method onError;
private final SessionListener sessionListener;
private final EndpointEventListener endpointEventListener;
private final ClusterContext clusterContext;
private final Session dummySession;
/**
* Create {@link TyrusEndpointWrapper} for class that extends {@link Endpoint}.
*
* @param endpointClass endpoint class for which the wrapper is created.
* @param configuration endpoint configuration.
* @param componentProvider component provider.
* @param container container where the wrapper is running.
* @param clusterContext cluster context instance. {@code null} indicates standalone mode.
* @param endpointEventListener endpoint event listener.
*/
public TyrusEndpointWrapper(Class<? extends Endpoint> endpointClass, EndpointConfig configuration,
ComponentProviderService componentProvider, WebSocketContainer container,
String contextPath, ServerEndpointConfig.Configurator configurator,
SessionListener sessionListener, ClusterContext clusterContext,
EndpointEventListener endpointEventListener) throws DeploymentException {
this(null, endpointClass, configuration, componentProvider, container, contextPath, configurator, sessionListener, clusterContext, endpointEventListener);
}
/**
* Create {@link TyrusEndpointWrapper} for {@link Endpoint} instance or {@link AnnotatedEndpoint} instance.
*
* @param endpoint endpoint instance for which the wrapper is created.
* @param configuration endpoint configuration.
* @param componentProvider component provider.
* @param container container where the wrapper is running.
* @param clusterContext cluster context instance. {@code null} indicates standalone mode.
* @param endpointEventListener endpoint event listener.
*/
public TyrusEndpointWrapper(Endpoint endpoint, EndpointConfig configuration, ComponentProviderService componentProvider, WebSocketContainer container,
String contextPath, ServerEndpointConfig.Configurator configurator, SessionListener sessionListener, ClusterContext clusterContext,
EndpointEventListener endpointEventListener) throws DeploymentException {
this(endpoint, null, configuration, componentProvider, container, contextPath, configurator, sessionListener, clusterContext, endpointEventListener);
}
private TyrusEndpointWrapper(Endpoint endpoint, Class<? extends Endpoint> endpointClass, EndpointConfig configuration,
ComponentProviderService componentProvider, WebSocketContainer container,
String contextPath, final ServerEndpointConfig.Configurator configurator,
SessionListener sessionListener, final ClusterContext clusterContext,
EndpointEventListener endpointEventListener) throws DeploymentException {
this.endpointClass = endpointClass;
this.endpoint = endpoint;
this.container = container;
this.contextPath = contextPath;
this.configurator = configurator;
this.sessionListener = sessionListener;
this.clusterContext = clusterContext;
if (endpointEventListener != null) {
this.endpointEventListener = endpointEventListener;
} else {
this.endpointEventListener = EndpointEventListener.NO_OP;
}
// server-side only
if (configuration instanceof ServerEndpointConfig) {
this.serverEndpointPath = ((ServerEndpointConfig) configuration).getPath();
this.endpointPath = (contextPath.endsWith("/") ? contextPath.substring(0, contextPath.length() - 1) : contextPath)
+ "/" + (serverEndpointPath.startsWith("/") ? serverEndpointPath.substring(1) : serverEndpointPath);
} else {
this.serverEndpointPath = null;
this.endpointPath = null;
}
this.componentProvider = configurator == null ? componentProvider : new ComponentProviderService(componentProvider) {
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
return configurator.getEndpointInstance(endpointClass);
}
};
{
final Class<? extends Endpoint> clazz = endpointClass == null ? endpoint.getClass() : endpointClass;
Method onOpenMethod = null;
Method onCloseMethod = null;
Method onErrorMethod = null;
for (Method m : Endpoint.class.getMethods()) {
if (m.getName().equals("onOpen")) {
onOpenMethod = m;
} else if (m.getName().equals("onClose")) {
onCloseMethod = m;
} else if (m.getName().equals("onError")) {
onErrorMethod = m;
}
}
try {
// Endpoint class contains all of these.
assert onOpenMethod != null;
assert onCloseMethod != null;
assert onErrorMethod != null;
onOpenMethod = clazz.getMethod(onOpenMethod.getName(), onOpenMethod.getParameterTypes());
onCloseMethod = clazz.getMethod(onCloseMethod.getName(), onCloseMethod.getParameterTypes());
onErrorMethod = clazz.getMethod(onErrorMethod.getName(), onErrorMethod.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new DeploymentException(e.getMessage(), e);
}
if (endpoint != null) {
this.onOpen = onOpenMethod;
this.onClose = onCloseMethod;
this.onError = onErrorMethod;
} else {
this.onOpen = componentProvider.getInvocableMethod(onOpenMethod);
this.onClose = componentProvider.getInvocableMethod(onCloseMethod);
this.onError = componentProvider.getInvocableMethod(onErrorMethod);
}
}
this.configuration = configuration == null ? new EndpointConfig() {
private final Map<String, Object> properties = new HashMap<String, Object>();
@Override
public List<Class<? extends Encoder>> getEncoders() {
return Collections.emptyList();
}
@Override
public List<Class<? extends Decoder>> getDecoders() {
return Collections.emptyList();
}
@Override
public Map<String, Object> getUserProperties() {
return properties;
}
} : configuration;
for (Class<? extends Decoder> decoderClass : this.configuration.getDecoders()) {
Class<?> type = getDecoderClassType(decoderClass);
decoders.add(new CoderWrapper<Decoder>(decoderClass, type));
}
//this wrapper represents endpoint which is not annotated endpoint
if (endpoint == null || !(endpoint instanceof AnnotatedEndpoint)) {
for (Class<? extends Decoder> decoderClass : getDefaultDecoders()) {
Class<?> type = getDecoderClassType(decoderClass);
decoders.add(new CoderWrapper<Decoder>(decoderClass, type));
}
}
for (Class<? extends Encoder> encoderClass : this.configuration.getEncoders()) {
Class<?> type = getEncoderClassType(encoderClass);
encoders.add(new CoderWrapper<Encoder>(encoderClass, type));
}
encoders.add(new CoderWrapper<Encoder>(NoOpTextCoder.class, String.class));
encoders.add(new CoderWrapper<Encoder>(NoOpByteBufferCoder.class, ByteBuffer.class));
encoders.add(new CoderWrapper<Encoder>(NoOpByteArrayCoder.class, byte[].class));
encoders.add(new CoderWrapper<Encoder>(ToStringEncoder.class, Object.class));
// clustered mode
if (clusterContext != null) {
dummySession = new ClusterSession(null, null, null, null, null);
clusterContext.registerSessionListener(getEndpointPath(), new org.glassfish.tyrus.core.cluster.SessionListener() {
@Override
public void onSessionOpened(String sessionId) {
final Map<ClusterSession.DistributedMapKey, Object> distributedSessionProperties = clusterContext.getDistributedSessionProperties(sessionId);
clusteredSessions.put(sessionId, new ClusterSession(sessionId, clusterContext, distributedSessionProperties, TyrusEndpointWrapper.this, dummySession));
}
@Override
public void onSessionClosed(String sessionId) {
clusteredSessions.remove(sessionId);
}
});
clusterContext.registerBroadcastListener(getEndpointPath(), new BroadcastListener() {
@Override
public void onBroadcast(String text) {
broadcast(text, true);
}
@Override
public void onBroadcast(byte[] data) {
broadcast(ByteBuffer.wrap(data), true);
}
});
for (String sessionId : clusterContext.getRemoteSessionIds(getEndpointPath())) {
final Map<ClusterSession.DistributedMapKey, Object> distributedSessionProperties = clusterContext.getDistributedSessionProperties(sessionId);
clusteredSessions.put(sessionId, new ClusterSession(sessionId, clusterContext, distributedSessionProperties, this, dummySession));
}
} else {
dummySession = null;
}
}
static List<Class<? extends Decoder>> getDefaultDecoders() {
final List<Class<? extends Decoder>> classList = new ArrayList<Class<? extends Decoder>>();
classList.addAll(PrimitiveDecoders.ALL);
classList.add(NoOpTextCoder.class);
classList.add(NoOpByteBufferCoder.class);
classList.add(NoOpByteArrayCoder.class);
classList.add(ReaderDecoder.class);
classList.add(InputStreamDecoder.class);
return classList;
}
private static URI getURI(String uri, String queryString) {
if (queryString != null && !queryString.isEmpty()) {
return URI.create(String.format("%s?%s", uri, queryString));
} else {
return URI.create(uri);
}
}
private <T> Object getCoderInstance(Session session, CoderWrapper<T> wrapper) {
final Object coder = wrapper.getCoder();
if (coder == null) {
ErrorCollector collector = new ErrorCollector();
final Object coderInstance = this.componentProvider.getCoderInstance(wrapper.getCoderClass(), session, getEndpointConfig(), collector);
if (!collector.isEmpty()) {
final DeploymentException deploymentException = collector.composeComprehensiveException();
LOGGER.log(Level.WARNING, deploymentException.getMessage(), deploymentException);
return null;
}
return coderInstance;
}
return coder;
}
Object decodeCompleteMessage(Session session, Object message, Class<?> type, CoderWrapper<Decoder> selectedDecoder) throws DecodeException, IOException {
final Class<? extends Decoder> decoderClass = selectedDecoder.getCoderClass();
if (Decoder.Text.class.isAssignableFrom(decoderClass)) {
if (type != null && type.isAssignableFrom(selectedDecoder.getType())) {
final Decoder.Text decoder = (Decoder.Text) getCoderInstance(session, selectedDecoder);
// TYRUS-210: willDecode was already called
return decoder.decode((String) message);
}
} else if (Decoder.Binary.class.isAssignableFrom(decoderClass)) {
if (type != null && type.isAssignableFrom(selectedDecoder.getType())) {
final Decoder.Binary decoder = (Decoder.Binary) getCoderInstance(session, selectedDecoder);
// TYRUS-210: willDecode was already called
return decoder.decode((ByteBuffer) message);
}
} else if (Decoder.TextStream.class.isAssignableFrom(decoderClass)) {
if (type != null && type.isAssignableFrom(selectedDecoder.getType())) {
return ((Decoder.TextStream) getCoderInstance(session, selectedDecoder)).decode(new StringReader((String) message));
}
} else if (Decoder.BinaryStream.class.isAssignableFrom(decoderClass)) {
if (type != null && type.isAssignableFrom(selectedDecoder.getType())) {
byte[] array = ((ByteBuffer) message).array();
return ((Decoder.BinaryStream) getCoderInstance(session, selectedDecoder)).decode(new ByteArrayInputStream(array));
}
}
return null;
}
private ArrayList<CoderWrapper<Decoder>> findApplicableDecoders(Session session, Object message, boolean isString) {
ArrayList<CoderWrapper<Decoder>> result = new ArrayList<CoderWrapper<Decoder>>();
for (CoderWrapper<Decoder> dec : decoders) {
if (isString && (Decoder.Text.class.isAssignableFrom(dec.getCoderClass()))) {
final Decoder.Text decoder = (Decoder.Text) getCoderInstance(session, dec);
if (decoder.willDecode((String) message)) {
result.add(dec);
}
} else if (!isString && (Decoder.Binary.class.isAssignableFrom(dec.getCoderClass()))) {
final Decoder.Binary decoder = (Decoder.Binary) getCoderInstance(session, dec);
if (decoder.willDecode((ByteBuffer) message)) {
result.add(dec);
}
} else if (isString && (Decoder.TextStream.class.isAssignableFrom(dec.getCoderClass()))) {
result.add(dec);
} else if (!isString && (Decoder.BinaryStream.class.isAssignableFrom(dec.getCoderClass()))) {
result.add(dec);
}
}
return result;
}
public Object doEncode(Session session, Object message) throws EncodeException, IOException {
for (CoderWrapper<Encoder> enc : encoders) {
final Class<? extends Encoder> encoderClass = enc.getCoderClass();
if (Encoder.Binary.class.isAssignableFrom(encoderClass)) {
if (enc.getType().isAssignableFrom(message.getClass())) {
final Encoder.Binary encoder = (Encoder.Binary) getCoderInstance(session, enc);
return encoder.encode(message);
}
} else if (Encoder.Text.class.isAssignableFrom(encoderClass)) {
if (enc.getType().isAssignableFrom(message.getClass())) {
final Encoder.Text encoder = (Encoder.Text) getCoderInstance(session, enc);
return encoder.encode(message);
}
} else if (Encoder.BinaryStream.class.isAssignableFrom(encoderClass)) {
if (enc.getType().isAssignableFrom(message.getClass())) {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final Encoder.BinaryStream encoder = (Encoder.BinaryStream) getCoderInstance(session, enc);
encoder.encode(message, stream);
return stream;
}
} else if (Encoder.TextStream.class.isAssignableFrom(encoderClass)) {
if (enc.getType().isAssignableFrom(message.getClass())) {
final Writer writer = new StringWriter();
final Encoder.TextStream encoder = (Encoder.TextStream) getCoderInstance(session, enc);
encoder.encode(message, writer);
return writer;
}
}
}
throw new EncodeException(message, LocalizationMessages.ENCODING_FAILED());
}
/**
* Server-side; Get Endpoint absolute path.
*
* @return endpoint absolute path.
*/
public String getEndpointPath() {
return endpointPath;
}
/**
* Server-side; Get server endpoint path.
* <p/>
* In this context, server endpoint path is exactly what is present in {@link javax.websocket.server.ServerEndpoint}
* annotation or returned from {@link javax.websocket.server.ServerEndpointConfig#getPath()} method call. Context
* path is not included.
*
* @return server endpoint path.
* @see javax.websocket.server.ServerEndpoint#value()
* @see javax.websocket.server.ServerEndpointConfig#getPath()
*/
public String getServerEndpointPath() {
return serverEndpointPath;
}
/**
* Server-side; Get the negotiated extensions' names based on the extensions supported by client.
*
* @param clientExtensions names of the extensions' supported by client.
* @return names of extensions supported by both client and class that implements this one.
*/
public List<Extension> getNegotiatedExtensions(List<Extension> clientExtensions) {
if (configuration instanceof ServerEndpointConfig) {
return configurator.getNegotiatedExtensions(((ServerEndpointConfig) configuration).getExtensions(), clientExtensions);
} else {
return Collections.emptyList();
}
}
/**
* Server-side; Compute the sub-protocol which will be used.
*
* @param clientProtocols sub-protocols supported by client.
* @return negotiated sub-protocol, {@code null} if none found.
*/
public String getNegotiatedProtocol(List<String> clientProtocols) {
if (configuration instanceof ServerEndpointConfig) {
return configurator.getNegotiatedSubprotocol(((ServerEndpointConfig) configuration).getSubprotocols(), clientProtocols);
} else {
return null;
}
}
/**
* Get the endpoint's open {@link Session}s.
*
* @param tyrusSession only for clustering purpose (local encoder will be used when needed).
* @return open sessions.
*/
public Set<Session> getOpenSessions(final TyrusSession tyrusSession) {
Set<Session> result = new HashSet<Session>();
for (Session session : webSocketToSession.values()) {
if (session.isOpen()) {
result.add(session);
}
}
// clustered mode
if (clusterContext != null) {
result.addAll(clusteredSessions.values());
}
return Collections.unmodifiableSet(result);
}
/**
* Creates a Session based on the {@link TyrusWebSocket}, subprotocols and extensions.
*
* @param socket the other end of the connection.
* @param subprotocol used.
* @param extensions extensions used.
* @return {@link Session} representing the connection.
*/
public Session createSessionForRemoteEndpoint(TyrusWebSocket socket, String subprotocol, List<Extension> extensions) {
final TyrusSession session = new TyrusSession(container, socket, this, subprotocol, extensions, false,
getURI(contextPath, null), null, Collections.<String, String>emptyMap(), null, Collections.<String, List<String>>emptyMap(), null, null, null);
webSocketToSession.put(socket, session);
return session;
}
private TyrusSession getSession(TyrusWebSocket socket) {
return webSocketToSession.get(socket);
}
/**
* Called by the provider when the web socket connection
* is established.
*
* @param socket {@link TyrusWebSocket} who has just connected to this web socket endpoint.
* @param upgradeRequest request associated with accepted connection.
* @return Created {@link Session} instance or {@code null} when session was not created properly (max sessions
* limit on endpoint or application or issues with endpoint validation).
*/
public Session onConnect(TyrusWebSocket socket, UpgradeRequest upgradeRequest, String subProtocol, List<Extension> extensions, String connectionId) {
TyrusSession session = webSocketToSession.get(socket);
// session is null on Server; client always has session instance at this point.
if (session == null) {
final Map<String, String> templateValues = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : upgradeRequest.getParameterMap().entrySet()) {
templateValues.put(entry.getKey(), entry.getValue().get(0));
}
// create a new session
session = new TyrusSession(container, socket, this, subProtocol, extensions, upgradeRequest.isSecure(),
getURI(upgradeRequest.getRequestURI().toString(), upgradeRequest.getQueryString()),
upgradeRequest.getQueryString(), templateValues, upgradeRequest.getUserPrincipal(),
upgradeRequest.getParameterMap(), clusterContext, connectionId,
((RequestContext) upgradeRequest).getRemoteAddr());
webSocketToSession.put(socket, session);
// max open session per endpoint exceeded?
boolean maxSessionPerEndpointExceeded = configuration instanceof TyrusServerEndpointConfig &&
((TyrusServerEndpointConfig) configuration).getMaxSessions() > 0 &&
webSocketToSession.size() > ((TyrusServerEndpointConfig) configuration).getMaxSessions();
SessionListener.OnOpenResult onOpenResult = sessionListener.onOpen(session);
// test max open sessions per endpoint and per application
if (maxSessionPerEndpointExceeded || !onOpenResult.equals(SessionListener.OnOpenResult.SESSION_ALLOWED)) {
try {
webSocketToSession.remove(socket);
String refuseDetail;
if (maxSessionPerEndpointExceeded) {
refuseDetail = LocalizationMessages.MAX_SESSIONS_PER_ENDPOINT_EXCEEDED();
} else {
switch (onOpenResult) {
case MAX_SESSIONS_PER_APP_EXCEEDED:
refuseDetail = LocalizationMessages.MAX_SESSIONS_PER_APP_EXCEEDED();
break;
case MAX_SESSIONS_PER_REMOTE_ADDR_EXCEEDED:
refuseDetail = LocalizationMessages.MAX_SESSIONS_PER_REMOTEADDR_EXCEEDED();
break;
default:
// should not happen.
refuseDetail = null;
}
}
LOGGER.log(Level.FINE, "Session opening refused: " + refuseDetail);
session.close(new CloseReason(CloseReason.CloseCodes.TRY_AGAIN_LATER, refuseDetail));
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
// session was not opened.
return null;
}
socket.setMessageEventListener(endpointEventListener.onSessionOpened(session.getId()));
}
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
// TYRUS-325: Server do not close session properly if non-instantiable endpoint class is provided
// programmatic non-instantiable endpoint has toCall null
if (toCall == null) {
if (!collector.isEmpty()) {
Throwable t = collector.composeComprehensiveException();
LOGGER.log(Level.FINE, t.getMessage(), t);
}
webSocketToSession.remove(socket);
sessionListener.onClose(session, CloseReasons.UNEXPECTED_CONDITION.getCloseReason());
try {
session.close(CloseReasons.UNEXPECTED_CONDITION.getCloseReason());
} catch (IOException e) {
LOGGER.log(Level.FINEST, e.getMessage(), e);
}
// session was not opened.
return null;
}
try {
if (!collector.isEmpty()) {
throw collector.composeComprehensiveException();
}
if (endpoint != null) {
((Endpoint) toCall).onOpen(session, configuration);
} else {
onOpen.invoke(toCall, session, configuration);
}
} catch (Throwable t) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
endpointEventListener.onError(session.getId(), t);
}
return session;
}
/**
* Called by the provider when the web socket connection
* has an incoming text message from the given remote endpoint.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param messageBytes the message.
*/
public void onMessage(TyrusWebSocket socket, ByteBuffer messageBytes) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Message received on already closed connection.");
return;
}
try {
session.restartIdleTimeoutExecutor();
final TyrusSession.State state = session.getState();
if (state == TyrusSession.State.RECEIVING_BINARY || state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
if (session.isWholeBinaryHandlerPresent()) {
session.notifyMessageHandlers(messageBytes, findApplicableDecoders(session, messageBytes, false));
} else if (session.isPartialBinaryHandlerPresent()) {
session.notifyMessageHandlers(messageBytes, true);
} else {
throw new IllegalStateException(LocalizationMessages.BINARY_MESSAGE_HANDLER_NOT_FOUND(session));
}
} catch (Throwable t) {
if (!processThrowable(t, session)) {
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
if (toCall != null) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
} else if (!collector.isEmpty()) {
final DeploymentException deploymentException = collector.composeComprehensiveException();
LOGGER.log(Level.WARNING, deploymentException.getMessage(), deploymentException);
}
endpointEventListener.onError(session.getId(), t);
}
}
}
/**
* Called by the provider when the web socket connection
* has an incoming text message from the given remote endpoint.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param messageString the message.
*/
public void onMessage(TyrusWebSocket socket, String messageString) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Message received on already closed connection.");
return;
}
try {
session.restartIdleTimeoutExecutor();
final TyrusSession.State state = session.getState();
if (state == TyrusSession.State.RECEIVING_BINARY || state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
if (session.isWholeTextHandlerPresent()) {
session.notifyMessageHandlers(messageString, findApplicableDecoders(session, messageString, true));
} else if (session.isPartialTextHandlerPresent()) {
session.notifyMessageHandlers(messageString, true);
} else {
throw new IllegalStateException(LocalizationMessages.TEXT_MESSAGE_HANDLER_NOT_FOUND(session));
}
} catch (Throwable t) {
if (!processThrowable(t, session)) {
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
if (toCall != null) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
} else if (!collector.isEmpty()) {
final DeploymentException deploymentException = collector.composeComprehensiveException();
LOGGER.log(Level.WARNING, deploymentException.getMessage(), deploymentException);
}
endpointEventListener.onError(session.getId(), t);
}
}
}
/**
* Called by the provider when the web socket connection
* has an incoming partial text message from the given remote endpoint. Partial
* text messages are passed in sequential order, one piece at a time. If an implementation
* does not support streaming, it will need to reconstruct the message here and pass the whole
* thing along.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param partialString the String message.
* @param last to indicate if this is the last partial string in the sequence
*/
public void onPartialMessage(TyrusWebSocket socket, String partialString, boolean last) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Message received on already closed connection.");
return;
}
try {
session.restartIdleTimeoutExecutor();
final TyrusSession.State state = session.getState();
if (session.isPartialTextHandlerPresent()) {
session.notifyMessageHandlers(partialString, last);
if (state == TyrusSession.State.RECEIVING_BINARY || state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
} else if (session.isReaderHandlerPresent()) {
ReaderBuffer buffer = session.getReaderBuffer();
switch (state) {
case RUNNING:
if (buffer == null) {
// TODO:
buffer = new ReaderBuffer(((BaseContainer) container).getExecutorService());
session.setReaderBuffer(buffer);
}
buffer.resetBuffer(session.getMaxTextMessageBufferSize());
buffer.setMessageHandler((session.getMessageHandler(Reader.class)));
buffer.appendMessagePart(partialString, last);
session.setState(TyrusSession.State.RECEIVING_TEXT);
break;
case RECEIVING_TEXT:
buffer.appendMessagePart(partialString, last);
if (last) {
session.setState(TyrusSession.State.RUNNING);
}
break;
default:
if (state == TyrusSession.State.RECEIVING_BINARY) {
session.setState(TyrusSession.State.RUNNING);
}
throw new IllegalStateException(LocalizationMessages.PARTIAL_TEXT_MESSAGE_OUT_OF_ORDER(session));
}
} else if (session.isWholeTextHandlerPresent()) {
switch (state) {
case RUNNING:
session.getTextBuffer().resetBuffer(session.getMaxTextMessageBufferSize());
session.getTextBuffer().appendMessagePart(partialString);
session.setState(TyrusSession.State.RECEIVING_TEXT);
break;
case RECEIVING_TEXT:
session.getTextBuffer().appendMessagePart(partialString);
if (last) {
final String message = session.getTextBuffer().getBufferedContent();
session.notifyMessageHandlers(message, findApplicableDecoders(session, message, true));
session.setState(TyrusSession.State.RUNNING);
}
break;
default:
if (state == TyrusSession.State.RECEIVING_BINARY) {
session.setState(TyrusSession.State.RUNNING);
}
throw new IllegalStateException(LocalizationMessages.TEXT_MESSAGE_OUT_OF_ORDER(session));
}
}
} catch (Throwable t) {
if (!processThrowable(t, session)) {
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
if (toCall != null) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
} else if (!collector.isEmpty()) {
final DeploymentException deploymentException = collector.composeComprehensiveException();
LOGGER.log(Level.WARNING, deploymentException.getMessage(), deploymentException);
}
endpointEventListener.onError(session.getId(), t);
}
}
}
/**
* Called by the provider when the web socket connection
* has an incoming partial binary message from the given remote endpoint. Partial
* binary messages are passed in sequential order, one piece at a time. If an implementation
* does not support streaming, it will need to reconstruct the message here and pass the whole
* thing along.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param partialBytes the piece of the binary message.
* @param last to indicate if this is the last partial byte buffer in the sequence
*/
public void onPartialMessage(TyrusWebSocket socket, ByteBuffer partialBytes, boolean last) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Message received on already closed connection.");
return;
}
try {
session.restartIdleTimeoutExecutor();
final TyrusSession.State state = session.getState();
if (session.isPartialBinaryHandlerPresent()) {
session.notifyMessageHandlers(partialBytes, last);
if (state == TyrusSession.State.RECEIVING_BINARY || state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
} else if (session.isInputStreamHandlerPresent()) {
InputStreamBuffer buffer = session.getInputStreamBuffer();
switch (state) {
case RUNNING:
if (buffer == null) {
// TODO
buffer = new InputStreamBuffer(((BaseContainer) container).getExecutorService());
session.setInputStreamBuffer(buffer);
}
buffer.resetBuffer(session.getMaxBinaryMessageBufferSize());
buffer.setMessageHandler((session.getMessageHandler(InputStream.class)));
buffer.appendMessagePart(partialBytes, last);
session.setState(TyrusSession.State.RECEIVING_BINARY);
break;
case RECEIVING_BINARY:
buffer.appendMessagePart(partialBytes, last);
if (last) {
session.setState(TyrusSession.State.RUNNING);
}
break;
default:
if (state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
throw new IllegalStateException(LocalizationMessages.PARTIAL_BINARY_MESSAGE_OUT_OF_ORDER(session));
}
} else if (session.isWholeBinaryHandlerPresent()) {
switch (state) {
case RUNNING:
session.getBinaryBuffer().resetBuffer(session.getMaxBinaryMessageBufferSize());
session.getBinaryBuffer().appendMessagePart(partialBytes);
session.setState(TyrusSession.State.RECEIVING_BINARY);
break;
case RECEIVING_BINARY:
session.getBinaryBuffer().appendMessagePart(partialBytes);
if (last) {
ByteBuffer bb = session.getBinaryBuffer().getBufferedContent();
session.notifyMessageHandlers(bb, findApplicableDecoders(session, bb, false));
session.setState(TyrusSession.State.RUNNING);
}
break;
default:
if (state == TyrusSession.State.RECEIVING_TEXT) {
session.setState(TyrusSession.State.RUNNING);
}
throw new IllegalStateException(LocalizationMessages.BINARY_MESSAGE_OUT_OF_ORDER(session));
}
}
} catch (Throwable t) {
if (!processThrowable(t, session)) {
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
if (toCall != null) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
} else if (!collector.isEmpty()) {
final DeploymentException deploymentException = collector.composeComprehensiveException();
LOGGER.log(Level.WARNING, deploymentException.getMessage(), deploymentException);
}
endpointEventListener.onError(session.getId(), t);
}
}
}
/**
* Check {@link Throwable} produced during {@link javax.websocket.OnMessage} annotated method call.
*
* @param throwable thrown {@link Throwable}.
* @param session {@link Session} related to {@link Throwable}.
* @return {@code true} when exception is handled within this method (framework produced it), {@code false}
* otherwise.
*/
private boolean processThrowable(Throwable throwable, Session session) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, String.format("Exception thrown while processing message. Session: '%session'.", session), throwable);
}
if (throwable instanceof WebSocketException) {
try {
session.close(((WebSocketException) throwable).getCloseReason());
return false;
} catch (IOException e) {
// we don't care.
}
}
return false;
}
/**
* Called by the provider when the web socket connection
* has an incoming pong message from the given remote endpoint.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param bytes the message.
*/
public void onPong(TyrusWebSocket socket, final ByteBuffer bytes) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Pong received on already closed connection.");
return;
}
session.restartIdleTimeoutExecutor();
if (session.isPongHandlerPreset()) {
session.notifyPongHandler(new PongMessage() {
@Override
public ByteBuffer getApplicationData() {
return bytes;
}
@Override
public String toString() {
return "PongMessage: " + bytes;
}
});
} else {
LOGGER.log(Level.FINE, String.format("Unhandled pong message. Session: '%s'", session));
}
}
/**
* Called by the provider when the web socket connection
* has an incoming ping message from the given remote endpoint.
* <p/>
* The endpoint needs to respond as soon as possible (see the websocket RFC).
* No involvement from application layer, there is no ping listener.
*
* @param socket {@link TyrusWebSocket} who sent the message.
* @param bytes the message.
*/
public void onPing(TyrusWebSocket socket, ByteBuffer bytes) {
TyrusSession session = getSession(socket);
if (session == null) {
LOGGER.log(Level.FINE, "Ping received on already closed connection.");
return;
}
session.restartIdleTimeoutExecutor();
try {
session.getBasicRemote().sendPong(bytes);
} catch (IOException e) {
// do nothing.
// we might consider calling onError, but there should be better defined exception.
}
}
/**
* Called by the provider when the web socket connection
* to the given remote endpoint has just closed.
*
* @param socket {@link TyrusWebSocket} who has just closed the connection.
*/
public void onClose(TyrusWebSocket socket, CloseReason closeReason) {
TyrusSession session = getSession(socket);
if (session == null) {
return;
}
session.setState(TyrusSession.State.CLOSED);
ErrorCollector collector = new ErrorCollector();
final Object toCall = endpoint != null ? endpoint :
componentProvider.getInstance(endpointClass, session, collector);
try {
if (!collector.isEmpty()) {
throw collector.composeComprehensiveException();
}
if (endpoint != null) {
((Endpoint) toCall).onClose(session, closeReason);
} else {
onClose.invoke(toCall, session, closeReason);
}
} catch (Throwable t) {
if (toCall != null) {
if (endpoint != null) {
((Endpoint) toCall).onError(session, t);
} else {
try {
onError.invoke(toCall, session, t);
} catch (Exception e) {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
}
} else {
LOGGER.log(Level.WARNING, t.getMessage(), t);
}
endpointEventListener.onError(session.getId(), t);
} finally {
session.setState(TyrusSession.State.CLOSED);
if (clusterContext != null) {
clusterContext.removeSession(session.getId(), getEndpointPath());
// TODO: check the close reason or something more descriptive to get the info
// TODO: about the proper reason. We don't want to destroy userProperties in case
// TODO: of node failure.
clusterContext.destroyDistributedUserProperties(session.getConnectionId());
}
webSocketToSession.remove(socket);
endpointEventListener.onSessionClosed(session.getId());
componentProvider.removeSession(session);
sessionListener.onClose(session, closeReason);
}
}
/**
* Get Endpoint configuration.
*
* @return configuration.
*/
public EndpointConfig getEndpointConfig() {
return configuration;
}
/**
* Broadcasts text message to all connected clients.
*
* @param message message to be broadcasted.
* @return map of sessions and futures for user to get the information about status of the message. Messages send
* from other cluster nodes are not included.
*/
public Map<Session, Future<?>> broadcast(final String message) {
return broadcast(message, false);
}
private Map<Session, Future<?>> broadcast(final String message, boolean local) {
if (!local && clusterContext != null) {
clusterContext.broadcastText(getEndpointPath(), message);
}
final Map<Session, Future<?>> futures = new HashMap<Session, Future<?>>();
byte[] frame = null;
for (Map.Entry<TyrusWebSocket, TyrusSession> e : webSocketToSession.entrySet()) {
if (e.getValue().isOpen()) {
final TyrusWebSocket webSocket = e.getKey();
final ProtocolHandler protocolHandler = webSocket.getProtocolHandler();
// we need to let protocol handler execute extensions if there are any
if (protocolHandler.hasExtensions()) {
byte[] tempFrame;
final Frame dataFrame = new TextFrame(message, false, true);
final ByteBuffer byteBuffer = webSocket.getProtocolHandler().frame(dataFrame);
tempFrame = new byte[byteBuffer.remaining()];
byteBuffer.get(tempFrame);
final Future<Frame> frameFuture = webSocket.sendRawFrame(ByteBuffer.wrap(tempFrame));
futures.put(e.getValue(), frameFuture);
} else {
if (frame == null) {
final Frame dataFrame = new TextFrame(message, false, true);
final ByteBuffer byteBuffer = webSocket.getProtocolHandler().frame(dataFrame);
frame = new byte[byteBuffer.remaining()];
byteBuffer.get(frame);
}
final Future<Frame> frameFuture = webSocket.sendRawFrame(ByteBuffer.wrap(frame));
futures.put(e.getValue(), frameFuture);
}
}
}
return futures;
}
/**
* Broadcasts binary message to all connected clients.
*
* @param message message to be broadcasted.
* @return map of sessions and futures for user to get the information about status of the message. Messages send
* from other cluster nodes are not included.
*/
public Map<Session, Future<?>> broadcast(final ByteBuffer message) {
return broadcast(message, false);
}
private Map<Session, Future<?>> broadcast(final ByteBuffer message, boolean local) {
final Map<Session, Future<?>> futures = new HashMap<Session, Future<?>>();
byte[] frame = null;
byte[] byteArrayMessage = Utils.getRemainingArray(message);
if (!local && clusterContext != null) {
clusterContext.broadcastBinary(getEndpointPath(), byteArrayMessage);
}
for (Map.Entry<TyrusWebSocket, TyrusSession> e : webSocketToSession.entrySet()) {
if (e.getValue().isOpen()) {
final TyrusWebSocket webSocket = e.getKey();
final ProtocolHandler protocolHandler = webSocket.getProtocolHandler();
// we need to let protocol handler execute extensions if there are any
if (protocolHandler.hasExtensions()) {
byte[] tempFrame;
final Frame dataFrame = new BinaryFrame(byteArrayMessage, false, true);
final ByteBuffer byteBuffer = webSocket.getProtocolHandler().frame(dataFrame);
tempFrame = new byte[byteBuffer.remaining()];
byteBuffer.get(tempFrame);
final Future<Frame> frameFuture = webSocket.sendRawFrame(ByteBuffer.wrap(tempFrame));
futures.put(e.getValue(), frameFuture);
} else {
if (frame == null) {
final Frame dataFrame = new BinaryFrame(byteArrayMessage, false, true);
final ByteBuffer byteBuffer = webSocket.getProtocolHandler().frame(dataFrame);
frame = new byte[byteBuffer.remaining()];
byteBuffer.get(frame);
}
final Future<Frame> frameFuture = webSocket.sendRawFrame(ByteBuffer.wrap(frame));
futures.put(e.getValue(), frameFuture);
}
}
}
return futures;
}
/**
* Registered {@link Decoder}s.
*
* @return {@link List} of registered {@link Decoder}s.
*/
List<Decoder> getDecoders() {
return (List<Decoder>) (List<?>) decoders;
}
private Class<?> getEncoderClassType(Class<?> encoderClass) {
if (Encoder.Binary.class.isAssignableFrom(encoderClass)) {
return ReflectionHelper.getClassType(encoderClass, Encoder.Binary.class);
} else if (Encoder.Text.class.isAssignableFrom(encoderClass)) {
return ReflectionHelper.getClassType(encoderClass, Encoder.Text.class);
} else if (Encoder.BinaryStream.class.isAssignableFrom(encoderClass)) {
return ReflectionHelper.getClassType(encoderClass, Encoder.BinaryStream.class);
} else if (Encoder.TextStream.class.isAssignableFrom(encoderClass)) {
return ReflectionHelper.getClassType(encoderClass, Encoder.TextStream.class);
} else {
return null;
}
}
private Class<?> getDecoderClassType(Class<?> decoderClass) {
if (Decoder.Binary.class.isAssignableFrom(decoderClass)) {
return ReflectionHelper.getClassType(decoderClass, Decoder.Binary.class);
} else if (Decoder.Text.class.isAssignableFrom(decoderClass)) {
return ReflectionHelper.getClassType(decoderClass, Decoder.Text.class);
} else if (Decoder.BinaryStream.class.isAssignableFrom(decoderClass)) {
return ReflectionHelper.getClassType(decoderClass, Decoder.BinaryStream.class);
} else if (Decoder.TextStream.class.isAssignableFrom(decoderClass)) {
return ReflectionHelper.getClassType(decoderClass, Decoder.TextStream.class);
} else {
return null;
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TyrusEndpointWrapper");
sb.append("{endpointClass=").append(endpointClass);
sb.append(", endpoint=").append(endpoint);
sb.append(", contextPath='").append(contextPath).append('\'');
sb.append('}');
return sb.toString();
}
/**
* Server side check for protocol specific information to determine whether the request can be upgraded.
* <p/>
* The default implementation will check for the presence of the
* {@code Upgrade} header with a value of {@code WebSocket}.
*
* @param request received {@link UpgradeRequest}.
* @return {@code true} if the request should be upgraded to a WebSocket connection.
* @throws HandshakeException when origin verification check returns {@code false}.
*/
public final boolean upgrade(UpgradeRequest request) throws HandshakeException {
final String upgradeHeader = request.getHeader(UpgradeRequest.UPGRADE);
if (request.getHeaders().get(UpgradeRequest.UPGRADE) != null &&
// RFC 6455, paragraph 4.2.1.3
UpgradeRequest.WEBSOCKET.equalsIgnoreCase(upgradeHeader)) {
if (!(configuration instanceof ServerEndpointConfig)) {
return false;
}
if (configurator.checkOrigin(request.getHeader("Origin"))) {
return true;
} else {
throw new HandshakeException(403, LocalizationMessages.ORIGIN_NOT_VERIFIED());
}
}
return false;
}
/**
* Factory method to create new {@link TyrusWebSocket} instances. Developers may
* wish to override this to return customized {@link TyrusWebSocket} implementations.
*
* @param handler the {@link ProtocolHandler} to use with the newly created
* {@link TyrusWebSocket}.
* @return TODO
*/
public TyrusWebSocket createSocket(final ProtocolHandler handler) {
return new TyrusWebSocket(handler, this);
}
/**
* This method will be invoked if an unexpected exception is caught by
* the WebSocket runtime.
*
* @param socket the websocket being processed at the time the
* exception occurred.
* @param t the unexpected exception.
* @return {@code true} if the WebSocket should be closed otherwise
* {@code false}.
*/
public boolean onError(TyrusWebSocket socket, Throwable t) {
Logger.getLogger(TyrusEndpointWrapper.class.getName()).log(Level.WARNING, LocalizationMessages.UNEXPECTED_ERROR_CONNECTION_CLOSE(), t);
return true;
}
/**
* Invoked when server side handshake is ready to send response.
* <p/>
* Changes in response parameter will be reflected in data sent back to client.
*
* @param request original request which caused this handshake.
* @param response response to be send.
*/
public void onHandShakeResponse(UpgradeRequest request, UpgradeResponse response) {
final EndpointConfig configuration = getEndpointConfig();
if (configuration instanceof ServerEndpointConfig) {
// http://java.net/jira/browse/TYRUS-62
final ServerEndpointConfig serverEndpointConfig = (ServerEndpointConfig) configuration;
serverEndpointConfig.getConfigurator().modifyHandshake(serverEndpointConfig, createHandshakeRequest(request),
response);
}
}
private HandshakeRequest createHandshakeRequest(final UpgradeRequest webSocketRequest) {
if (webSocketRequest instanceof RequestContext) {
final RequestContext requestContext = (RequestContext) webSocketRequest;
// TYRUS-208; spec requests headers to be read only when passed to ServerEndpointConfig.Configurator#modifyHandshake.
// TYRUS-211; spec requests parameterMap to be read only when passed to ServerEndpointConfig.Configurator#modifyHandshake.
requestContext.lock();
return requestContext;
}
return null;
}
/**
* Session listener.
* <p/>
* TODO: rename/consolidate with {@link org.glassfish.tyrus.core.monitoring.EndpointEventListener}?
*/
public abstract static class SessionListener {
/**
* Result of {@link org.glassfish.tyrus.core.TyrusEndpointWrapper.SessionListener#onOpen(TyrusSession)}.
*/
public enum OnOpenResult {
/**
* Session can be opened.
*/
SESSION_ALLOWED,
/**
* Session cannot be opened - the maximal number of open session per application exceeded.
*/
MAX_SESSIONS_PER_APP_EXCEEDED,
/**
* Session cannot be opened - the maximal number of open session per remote address exceeded.
*/
MAX_SESSIONS_PER_REMOTE_ADDR_EXCEEDED
}
/**
* Invoked before {@link javax.websocket.OnOpen} annotated method
* or {@link Endpoint#onOpen(javax.websocket.Session, javax.websocket.EndpointConfig)} is invoked.
* <p/>
* Default implementation always returns {@link org.glassfish.tyrus.core.TyrusEndpointWrapper.SessionListener.OnOpenResult#SESSION_ALLOWED}.
*
* @param session session to be opened.
* @return {@link org.glassfish.tyrus.core.TyrusEndpointWrapper.SessionListener.OnOpenResult#SESSION_ALLOWED}
* if session can be opened or reason why not.
*/
public OnOpenResult onOpen(final TyrusSession session) {
return OnOpenResult.SESSION_ALLOWED;
}
/**
* Invoked after {@link javax.websocket.OnClose} annotated method
* or {@link Endpoint#onClose(javax.websocket.Session, javax.websocket.CloseReason)} execution.
*
* @param session closed session.
* @param closeReason close reason.
*/
public void onClose(final TyrusSession session, final CloseReason closeReason) {
}
}
}