package de.rwth.idsg.bikeman.ixsi.endpoint; import de.rwth.idsg.bikeman.ixsi.IXSIConstants; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** * Delegates all WebSocketHandler method calls, which use the thread unsafe WebSocketSession, * to methods which use ConcurrentWebSocketSessionDecorator instead. * * To prevent exceptions like: * * IllegalStateException: The remote endpoint was in state [TEXT_PARTIAL_WRITING] * which is an invalid state for called method * * This happens, when req/res communication and sub push messages try to use the same session at the same time. * * @author Sevket Goekay <goekay@dbis.rwth-aachen.de> * @since 05.10.2015 */ public abstract class ConcurrentTextWebSocketHandler extends TextWebSocketHandler { private static final int sendTimeLimit = (int) TimeUnit.SECONDS.toMillis(10); private static final int bufferSizeLimit = 5 * IXSIConstants.MAX_TEXT_MSG_SIZE; private final Map<String, ConcurrentWebSocketSessionDecorator> sessions = new ConcurrentHashMap<>(); // ------------------------------------------------------------------------- // Delegate methods // ------------------------------------------------------------------------- @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { ConcurrentWebSocketSessionDecorator decorator = new ConcurrentWebSocketSessionDecorator(session, sendTimeLimit, bufferSizeLimit); sessions.put(session.getId(), decorator); this.onOpen(decorator); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { ConcurrentWebSocketSessionDecorator decorator = sessions.remove(session.getId()); this.onClose(decorator, closeStatus); } @Override public void handleTextMessage(WebSocketSession session, TextMessage webSocketMessage) throws Exception { this.onMessage(internalGet(session), webSocketMessage); } @Override public void handleTransportError(WebSocketSession session, Throwable throwable) throws Exception { this.onError(internalGet(session), throwable); } private ConcurrentWebSocketSessionDecorator internalGet(WebSocketSession session) { ConcurrentWebSocketSessionDecorator decorator = sessions.get(session.getId()); if (decorator == null) { throw new NoSuchElementException(); } return decorator; } // ------------------------------------------------------------------------- // Implement in extending classes // ------------------------------------------------------------------------- abstract void onMessage(WebSocketSession session, TextMessage webSocketMessage) throws Exception; abstract void onOpen(WebSocketSession session) throws Exception; abstract void onClose(WebSocketSession session, CloseStatus closeStatus) throws Exception; abstract void onError(WebSocketSession session, Throwable throwable) throws Exception; }