// // ======================================================================== // 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; } }