001/* 002 * Copyright 2002-2017 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; 018 019import java.io.IOException; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.util.Assert; 027import org.springframework.web.socket.BinaryMessage; 028import org.springframework.web.socket.CloseStatus; 029import org.springframework.web.socket.PingMessage; 030import org.springframework.web.socket.PongMessage; 031import org.springframework.web.socket.TextMessage; 032import org.springframework.web.socket.WebSocketMessage; 033import org.springframework.web.socket.WebSocketSession; 034 035/** 036 * An abstract base class for implementations of {@link WebSocketSession}. 037 * 038 * @author Rossen Stoyanchev 039 * @since 4.0 040 */ 041public abstract class AbstractWebSocketSession<T> implements NativeWebSocketSession { 042 043 protected static final Log logger = LogFactory.getLog(NativeWebSocketSession.class); 044 045 private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>(); 046 047 private T nativeSession; 048 049 050 /** 051 * Create a new instance and associate the given attributes with it. 052 * @param attributes attributes from the HTTP handshake to associate with the WebSocket 053 * session; the provided attributes are copied, the original map is not used. 054 */ 055 public AbstractWebSocketSession(Map<String, Object> attributes) { 056 if (attributes != null) { 057 this.attributes.putAll(attributes); 058 } 059 } 060 061 062 @Override 063 public Map<String, Object> getAttributes() { 064 return this.attributes; 065 } 066 067 @Override 068 public T getNativeSession() { 069 return this.nativeSession; 070 } 071 072 @SuppressWarnings("unchecked") 073 @Override 074 public <R> R getNativeSession(Class<R> requiredType) { 075 if (requiredType != null) { 076 if (requiredType.isInstance(this.nativeSession)) { 077 return (R) this.nativeSession; 078 } 079 } 080 return null; 081 } 082 083 public void initializeNativeSession(T session) { 084 Assert.notNull(session, "WebSocket session must not be null"); 085 this.nativeSession = session; 086 } 087 088 protected final void checkNativeSessionInitialized() { 089 Assert.state(this.nativeSession != null, "WebSocket session is not yet initialized"); 090 } 091 092 @Override 093 public final void sendMessage(WebSocketMessage<?> message) throws IOException { 094 checkNativeSessionInitialized(); 095 096 if (logger.isTraceEnabled()) { 097 logger.trace("Sending " + message + ", " + this); 098 } 099 100 if (message instanceof TextMessage) { 101 sendTextMessage((TextMessage) message); 102 } 103 else if (message instanceof BinaryMessage) { 104 sendBinaryMessage((BinaryMessage) message); 105 } 106 else if (message instanceof PingMessage) { 107 sendPingMessage((PingMessage) message); 108 } 109 else if (message instanceof PongMessage) { 110 sendPongMessage((PongMessage) message); 111 } 112 else { 113 throw new IllegalStateException("Unexpected WebSocketMessage type: " + message); 114 } 115 } 116 117 protected abstract void sendTextMessage(TextMessage message) throws IOException; 118 119 protected abstract void sendBinaryMessage(BinaryMessage message) throws IOException; 120 121 protected abstract void sendPingMessage(PingMessage message) throws IOException; 122 123 protected abstract void sendPongMessage(PongMessage message) throws IOException; 124 125 126 @Override 127 public final void close() throws IOException { 128 close(CloseStatus.NORMAL); 129 } 130 131 @Override 132 public final void close(CloseStatus status) throws IOException { 133 checkNativeSessionInitialized(); 134 if (logger.isDebugEnabled()) { 135 logger.debug("Closing " + this); 136 } 137 closeInternal(status); 138 } 139 140 protected abstract void closeInternal(CloseStatus status) throws IOException; 141 142 143 @Override 144 public String toString() { 145 if (this.nativeSession != null) { 146 return getClass().getSimpleName() + "[id=" + getId() + ", uri=" + getUri() + "]"; 147 } 148 else { 149 return getClass().getSimpleName() + "[nativeSession=null]"; 150 } 151 } 152 153}