blob: bb9d3883d504a73e09a377ae0e7e5403fad7fc2c [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356;
import java.io.IOException;
import java.net.URI;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
import javax.websocket.MessageHandler;
import javax.websocket.RemoteEndpoint.Async;
import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver;
import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata;
import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata;
/**
* Session for the JSR.
*/
public class JsrSession extends WebSocketSession implements javax.websocket.Session, Configurable
{
private static final Logger LOG = Log.getLogger(JsrSession.class);
private final ClientContainer container;
private final String id;
private final EndpointConfig config;
private final EndpointMetadata metadata;
private final DecoderFactory decoderFactory;
private final EncoderFactory encoderFactory;
/** Factory for MessageHandlers */
private final MessageHandlerFactory messageHandlerFactory;
/** Array of MessageHandlerWrappers, indexed by {@link MessageType#ordinal()} */
private final MessageHandlerWrapper wrappers[];
private Set<MessageHandler> messageHandlerSet;
private List<Extension> negotiatedExtensions;
private Map<String, String> pathParameters = new HashMap<>();
private JsrAsyncRemote asyncRemote;
private JsrBasicRemote basicRemote;
public JsrSession(ClientContainer container, String id, URI requestURI, EventDriver websocket, LogicalConnection connection)
{
super(container, requestURI, websocket, connection);
if (!(websocket instanceof AbstractJsrEventDriver))
{
throw new IllegalArgumentException("Cannot use, not a JSR WebSocket: " + websocket);
}
AbstractJsrEventDriver jsr = (AbstractJsrEventDriver)websocket;
this.config = jsr.getConfig();
this.metadata = jsr.getMetadata();
this.container = container;
this.id = id;
this.decoderFactory = new DecoderFactory(this,metadata.getDecoders(),container.getDecoderFactory());
this.encoderFactory = new EncoderFactory(this,metadata.getEncoders(),container.getEncoderFactory());
this.messageHandlerFactory = new MessageHandlerFactory();
this.wrappers = new MessageHandlerWrapper[MessageType.values().length];
this.messageHandlerSet = new HashSet<>();
}
@Override
public void addMessageHandler(MessageHandler handler) throws IllegalStateException
{
Objects.requireNonNull(handler, "MessageHandler cannot be null");
synchronized (wrappers)
{
for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
{
DecoderFactory.Wrapper wrapper = decoderFactory.getWrapperFor(metadata.getMessageClass());
if (wrapper == null)
{
StringBuilder err = new StringBuilder();
err.append("Unable to find decoder for type <");
err.append(metadata.getMessageClass().getName());
err.append("> used in <");
err.append(metadata.getHandlerClass().getName());
err.append(">");
throw new IllegalStateException(err.toString());
}
MessageType key = wrapper.getMetadata().getMessageType();
MessageHandlerWrapper other = wrappers[key.ordinal()];
if (other != null)
{
StringBuilder err = new StringBuilder();
err.append("Encountered duplicate MessageHandler handling message type <");
err.append(wrapper.getMetadata().getObjectType().getName());
err.append(">, ").append(metadata.getHandlerClass().getName());
err.append("<");
err.append(metadata.getMessageClass().getName());
err.append("> and ");
err.append(other.getMetadata().getHandlerClass().getName());
err.append("<");
err.append(other.getMetadata().getMessageClass().getName());
err.append("> both implement this message type");
throw new IllegalStateException(err.toString());
}
else
{
MessageHandlerWrapper handlerWrapper = new MessageHandlerWrapper(handler,metadata,wrapper);
wrappers[key.ordinal()] = handlerWrapper;
}
}
// Update handlerSet
updateMessageHandlerSet();
}
}
@Override
public void close(CloseReason closeReason) throws IOException
{
close(closeReason.getCloseCode().getCode(),closeReason.getReasonPhrase());
}
@Override
public Async getAsyncRemote()
{
if (asyncRemote == null)
{
asyncRemote = new JsrAsyncRemote(this);
}
return asyncRemote;
}
@Override
public Basic getBasicRemote()
{
if (basicRemote == null)
{
basicRemote = new JsrBasicRemote(this);
}
return basicRemote;
}
@Override
public WebSocketContainer getContainer()
{
return this.container;
}
public DecoderFactory getDecoderFactory()
{
return decoderFactory;
}
public EncoderFactory getEncoderFactory()
{
return encoderFactory;
}
public EndpointConfig getEndpointConfig()
{
return config;
}
public EndpointMetadata getEndpointMetadata()
{
return metadata;
}
@Override
public String getId()
{
return this.id;
}
@Override
public int getMaxBinaryMessageBufferSize()
{
return getPolicy().getMaxBinaryMessageSize();
}
@Override
public long getMaxIdleTimeout()
{
return getPolicy().getIdleTimeout();
}
@Override
public int getMaxTextMessageBufferSize()
{
return getPolicy().getMaxTextMessageSize();
}
public MessageHandlerFactory getMessageHandlerFactory()
{
return messageHandlerFactory;
}
@Override
public Set<MessageHandler> getMessageHandlers()
{
// Always return copy of set, as it is common to iterate and remove from the real set.
return new HashSet<MessageHandler>(messageHandlerSet);
}
public MessageHandlerWrapper getMessageHandlerWrapper(MessageType type)
{
synchronized (wrappers)
{
return wrappers[type.ordinal()];
}
}
@Override
public List<Extension> getNegotiatedExtensions()
{
if ((negotiatedExtensions == null) && getUpgradeResponse().getExtensions() != null)
{
negotiatedExtensions = getUpgradeResponse().getExtensions().stream().map(JsrExtension::new).collect(Collectors.toList());
}
return negotiatedExtensions;
}
@Override
public String getNegotiatedSubprotocol()
{
String acceptedSubProtocol = getUpgradeResponse().getAcceptedSubProtocol();
if (acceptedSubProtocol == null)
{
return "";
}
return acceptedSubProtocol;
}
@Override
public Set<Session> getOpenSessions()
{
return container.getOpenSessions();
}
@Override
public Map<String, String> getPathParameters()
{
return Collections.unmodifiableMap(pathParameters);
}
@Override
public String getQueryString()
{
return getUpgradeRequest().getRequestURI().getQuery();
}
@Override
public Map<String, List<String>> getRequestParameterMap()
{
return getUpgradeRequest().getParameterMap();
}
@Override
public Principal getUserPrincipal()
{
return getUpgradeRequest().getUserPrincipal();
}
@Override
public Map<String, Object> getUserProperties()
{
return config.getUserProperties();
}
@Override
public void init(EndpointConfig config)
{
// Initialize encoders
encoderFactory.init(config);
// Initialize decoders
decoderFactory.init(config);
}
@Override
public void removeMessageHandler(MessageHandler handler)
{
synchronized (wrappers)
{
try
{
for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass()))
{
DecoderMetadata decoder = decoderFactory.getMetadataFor(metadata.getMessageClass());
MessageType key = decoder.getMessageType();
wrappers[key.ordinal()] = null;
}
updateMessageHandlerSet();
}
catch (IllegalStateException e)
{
LOG.warn("Unable to identify MessageHandler: " + handler.getClass().getName(),e);
}
}
}
@Override
public void setMaxBinaryMessageBufferSize(int length)
{
getPolicy().setMaxBinaryMessageSize(length);
getPolicy().setMaxBinaryMessageBufferSize(length);
}
@Override
public void setMaxIdleTimeout(long milliseconds)
{
getPolicy().setIdleTimeout(milliseconds);
super.setIdleTimeout(milliseconds);
}
@Override
public void setMaxTextMessageBufferSize(int length)
{
getPolicy().setMaxTextMessageSize(length);
getPolicy().setMaxTextMessageBufferSize(length);
}
public void setPathParameters(Map<String, String> pathParams)
{
this.pathParameters.clear();
if (pathParams != null)
{
this.pathParameters.putAll(pathParams);
}
}
private void updateMessageHandlerSet()
{
messageHandlerSet.clear();
for (MessageHandlerWrapper wrapper : wrappers)
{
if (wrapper == null)
{
// skip empty
continue;
}
messageHandlerSet.add(wrapper.getHandler());
}
}
@Override
public BatchMode getBatchMode()
{
// JSR 356 specification mandates default batch mode to be off.
return BatchMode.OFF;
}
}