001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.socket.adapter.jetty;
018
019import java.nio.ByteBuffer;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import org.eclipse.jetty.websocket.api.Session;
024import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
025import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
026import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
027import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
028import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
029import org.eclipse.jetty.websocket.api.annotations.WebSocket;
030import org.eclipse.jetty.websocket.api.extensions.Frame;
031import org.eclipse.jetty.websocket.common.OpCode;
032
033import org.springframework.util.Assert;
034import org.springframework.web.socket.BinaryMessage;
035import org.springframework.web.socket.CloseStatus;
036import org.springframework.web.socket.PongMessage;
037import org.springframework.web.socket.TextMessage;
038import org.springframework.web.socket.WebSocketHandler;
039import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
040
041/**
042 * Adapts {@link WebSocketHandler} to the Jetty 9 WebSocket API.
043 *
044 * @author Rossen Stoyanchev
045 * @since 4.0
046 */
047@WebSocket
048public class JettyWebSocketHandlerAdapter {
049
050        private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
051
052        private static final Log logger = LogFactory.getLog(JettyWebSocketHandlerAdapter.class);
053
054
055        private final WebSocketHandler webSocketHandler;
056
057        private final JettyWebSocketSession wsSession;
058
059
060        public JettyWebSocketHandlerAdapter(WebSocketHandler webSocketHandler, JettyWebSocketSession wsSession) {
061                Assert.notNull(webSocketHandler, "WebSocketHandler must not be null");
062                Assert.notNull(wsSession, "WebSocketSession must not be null");
063                this.webSocketHandler = webSocketHandler;
064                this.wsSession = wsSession;
065        }
066
067
068        @OnWebSocketConnect
069        public void onWebSocketConnect(Session session) {
070                try {
071                        this.wsSession.initializeNativeSession(session);
072                        this.webSocketHandler.afterConnectionEstablished(this.wsSession);
073                }
074                catch (Throwable ex) {
075                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
076                }
077        }
078
079        @OnWebSocketMessage
080        public void onWebSocketText(String payload) {
081                TextMessage message = new TextMessage(payload);
082                try {
083                        this.webSocketHandler.handleMessage(this.wsSession, message);
084                }
085                catch (Throwable ex) {
086                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
087                }
088        }
089
090        @OnWebSocketMessage
091        public void onWebSocketBinary(byte[] payload, int offset, int length) {
092                BinaryMessage message = new BinaryMessage(payload, offset, length, true);
093                try {
094                        this.webSocketHandler.handleMessage(this.wsSession, message);
095                }
096                catch (Throwable ex) {
097                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
098                }
099        }
100
101        @OnWebSocketFrame
102        public void onWebSocketFrame(Frame frame) {
103                if (OpCode.PONG == frame.getOpCode()) {
104                        ByteBuffer payload = frame.getPayload() != null ? frame.getPayload() : EMPTY_PAYLOAD;
105                        PongMessage message = new PongMessage(payload);
106                        try {
107                                this.webSocketHandler.handleMessage(this.wsSession, message);
108                        }
109                        catch (Throwable ex) {
110                                ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
111                        }
112                }
113        }
114
115        @OnWebSocketClose
116        public void onWebSocketClose(int statusCode, String reason) {
117                CloseStatus closeStatus = new CloseStatus(statusCode, reason);
118                try {
119                        this.webSocketHandler.afterConnectionClosed(this.wsSession, closeStatus);
120                }
121                catch (Throwable ex) {
122                        if (logger.isWarnEnabled()) {
123                                logger.warn("Unhandled exception after connection closed for " + this, ex);
124                        }
125                }
126        }
127
128        @OnWebSocketError
129        public void onWebSocketError(Throwable cause) {
130                try {
131                        this.webSocketHandler.handleTransportError(this.wsSession, cause);
132                }
133                catch (Throwable ex) {
134                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
135                }
136        }
137
138}