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