001/*
002 * Copyright 2002-2020 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.standard;
018
019import java.nio.ByteBuffer;
020
021import javax.websocket.CloseReason;
022import javax.websocket.Endpoint;
023import javax.websocket.EndpointConfig;
024import javax.websocket.MessageHandler;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.util.Assert;
030import org.springframework.web.socket.BinaryMessage;
031import org.springframework.web.socket.CloseStatus;
032import org.springframework.web.socket.PongMessage;
033import org.springframework.web.socket.TextMessage;
034import org.springframework.web.socket.WebSocketHandler;
035import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
036
037/**
038 * Adapts a {@link WebSocketHandler} to the standard WebSocket for Java API.
039 *
040 * @author Rossen Stoyanchev
041 * @since 4.0
042 */
043public class StandardWebSocketHandlerAdapter extends Endpoint {
044
045        private final Log logger = LogFactory.getLog(StandardWebSocketHandlerAdapter.class);
046
047        private final WebSocketHandler handler;
048
049        private final StandardWebSocketSession wsSession;
050
051
052        public StandardWebSocketHandlerAdapter(WebSocketHandler handler, StandardWebSocketSession wsSession) {
053                Assert.notNull(handler, "WebSocketHandler must not be null");
054                Assert.notNull(wsSession, "WebSocketSession must not be null");
055                this.handler = handler;
056                this.wsSession = wsSession;
057        }
058
059
060        @Override
061        public void onOpen(final javax.websocket.Session session, EndpointConfig config) {
062                this.wsSession.initializeNativeSession(session);
063
064                // The following inner classes need to remain since lambdas would not retain their
065                // declared generic types (which need to be seen by the underlying WebSocket engine)
066
067                if (this.handler.supportsPartialMessages()) {
068                        session.addMessageHandler(new MessageHandler.Partial<String>() {
069                                @Override
070                                public void onMessage(String message, boolean isLast) {
071                                        handleTextMessage(session, message, isLast);
072                                }
073                        });
074                        session.addMessageHandler(new MessageHandler.Partial<ByteBuffer>() {
075                                @Override
076                                public void onMessage(ByteBuffer message, boolean isLast) {
077                                        handleBinaryMessage(session, message, isLast);
078                                }
079                        });
080                }
081                else {
082                        session.addMessageHandler(new MessageHandler.Whole<String>() {
083                                @Override
084                                public void onMessage(String message) {
085                                        handleTextMessage(session, message, true);
086                                }
087                        });
088                        session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
089                                @Override
090                                public void onMessage(ByteBuffer message) {
091                                        handleBinaryMessage(session, message, true);
092                                }
093                        });
094                }
095
096                session.addMessageHandler(new MessageHandler.Whole<javax.websocket.PongMessage>() {
097                        @Override
098                        public void onMessage(javax.websocket.PongMessage message) {
099                                handlePongMessage(session, message.getApplicationData());
100                        }
101                });
102
103                try {
104                        this.handler.afterConnectionEstablished(this.wsSession);
105                }
106                catch (Exception ex) {
107                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
108                }
109        }
110
111        private void handleTextMessage(javax.websocket.Session session, String payload, boolean isLast) {
112                TextMessage textMessage = new TextMessage(payload, isLast);
113                try {
114                        this.handler.handleMessage(this.wsSession, textMessage);
115                }
116                catch (Exception ex) {
117                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
118                }
119        }
120
121        private void handleBinaryMessage(javax.websocket.Session session, ByteBuffer payload, boolean isLast) {
122                BinaryMessage binaryMessage = new BinaryMessage(payload, isLast);
123                try {
124                        this.handler.handleMessage(this.wsSession, binaryMessage);
125                }
126                catch (Exception ex) {
127                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
128                }
129        }
130
131        private void handlePongMessage(javax.websocket.Session session, ByteBuffer payload) {
132                PongMessage pongMessage = new PongMessage(payload);
133                try {
134                        this.handler.handleMessage(this.wsSession, pongMessage);
135                }
136                catch (Exception ex) {
137                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
138                }
139        }
140
141        @Override
142        public void onClose(javax.websocket.Session session, CloseReason reason) {
143                CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase());
144                try {
145                        this.handler.afterConnectionClosed(this.wsSession, closeStatus);
146                }
147                catch (Exception ex) {
148                        if (logger.isWarnEnabled()) {
149                                logger.warn("Unhandled on-close exception for " + this.wsSession, ex);
150                        }
151                }
152        }
153
154        @Override
155        public void onError(javax.websocket.Session session, Throwable exception) {
156                try {
157                        this.handler.handleTransportError(this.wsSession, exception);
158                }
159                catch (Exception ex) {
160                        ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, ex, logger);
161                }
162        }
163
164}