/** * Extract the SiMP session attributes from the given message and * wrap them in a {@link SimpAttributes} instance. * @param message the message to extract session attributes from */ public static SimpAttributes fromMessage(Message<?> message) { Assert.notNull(message, "Message must not be null"); MessageHeaders headers = message.getHeaders(); String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(headers); if (sessionId == null) { throw new IllegalStateException("No session id in " + message); } if (sessionAttributes == null) { throw new IllegalStateException("No session attributes in " + message); } return new SimpAttributes(sessionId, sessionAttributes); }
@Override public void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel) { this.decoders.remove(session.getId()); Message<byte[]> message = createDisconnectMessage(session); SimpAttributes simpAttributes = SimpAttributes.fromMessage(message); try { SimpAttributesContextHolder.setAttributes(simpAttributes); if (this.eventPublisher != null) { Principal user = getUser(session); publishEvent(this.eventPublisher, new SessionDisconnectEvent(this, message, session.getId(), closeStatus, user)); } outputChannel.send(message); } finally { this.stompAuthentications.remove(session.getId()); SimpAttributesContextHolder.resetAttributes(); simpAttributes.sessionCompleted(); } }
@Override public Object get(String name, ObjectFactory<?> objectFactory) { SimpAttributes simpAttributes = SimpAttributesContextHolder.currentAttributes(); Object scopedObject = simpAttributes.getAttribute(name); if (scopedObject != null) { return scopedObject; } synchronized (simpAttributes.getSessionMutex()) { scopedObject = simpAttributes.getAttribute(name); if (scopedObject == null) { scopedObject = objectFactory.getObject(); simpAttributes.setAttribute(name, scopedObject); } return scopedObject; } }
/** * Invoked when the session is completed. Executed completion callbacks. */ public void sessionCompleted() { synchronized (getSessionMutex()) { if (!isSessionCompleted()) { executeDestructionCallbacks(); this.attributes.put(SESSION_COMPLETED_NAME, Boolean.TRUE); } } }
/** * Register a callback to execute on destruction of the specified attribute. * The callback is executed when the session is closed. * @param name the name of the attribute to register the callback for * @param callback the destruction callback to be executed */ public void registerDestructionCallback(String name, Runnable callback) { synchronized (getSessionMutex()) { if (isSessionCompleted()) { throw new IllegalStateException("Session id=" + getSessionId() + " already completed"); } this.attributes.put(DESTRUCTION_CALLBACK_NAME_PREFIX + name, callback); } }
@Override @Nullable public Object remove(String name) { SimpAttributes simpAttributes = SimpAttributesContextHolder.currentAttributes(); synchronized (simpAttributes.getSessionMutex()) { Object value = simpAttributes.getAttribute(name); if (value != null) { simpAttributes.removeAttribute(name); return value; } else { return null; } } }
@Test public void webSocketScope() { Runnable runnable = Mockito.mock(Runnable.class); SimpAttributes simpAttributes = new SimpAttributes(this.session.getId(), this.session.getAttributes()); simpAttributes.setAttribute("name", "value"); simpAttributes.registerDestructionCallback("name", runnable); MessageChannel testChannel = new MessageChannel() { @Override public boolean send(Message<?> message) { SimpAttributes simpAttributes = SimpAttributesContextHolder.currentAttributes(); assertThat(simpAttributes.getAttribute("name"), is("value")); return true; } @Override public boolean send(Message<?> message, long timeout) { return false; } }; this.protocolHandler.afterSessionStarted(this.session, this.channel); StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT); Message<byte[]> message = MessageBuilder.createMessage(EMPTY_PAYLOAD, headers.getMessageHeaders()); TextMessage textMessage = new TextMessage(new StompEncoder().encode(message)); this.protocolHandler.handleMessageFromClient(this.session, textMessage, testChannel); assertEquals(Collections.<WebSocketMessage<?>>emptyList(), session.getSentMessages()); this.protocolHandler.afterSessionEnded(this.session, CloseStatus.BAD_DATA, testChannel); assertEquals(Collections.<WebSocketMessage<?>>emptyList(), this.session.getSentMessages()); verify(runnable, times(1)).run(); }
@Test public void sessionCompletedIsIdempotent() { Runnable callback1 = Mockito.mock(Runnable.class); this.simpAttributes.registerDestructionCallback("name1", callback1); this.simpAttributes.sessionCompleted(); this.simpAttributes.sessionCompleted(); this.simpAttributes.sessionCompleted(); verify(callback1, times(1)).run(); }
@Test public void getAttribute() { this.simpAttributes.setAttribute("name1", "value1"); assertThat(this.simpAttributes.getAttribute("name1"), is("value1")); assertThat(this.simpAttributes.getAttribute("name2"), nullValue()); }
@Test public void registerDestructionCallback() { Runnable callback = Mockito.mock(Runnable.class); this.simpAttributes.registerDestructionCallback("name1", callback); assertThat(this.simpAttributes.getAttribute( SimpAttributes.DESTRUCTION_CALLBACK_NAME_PREFIX + "name1"), sameInstance(callback)); }
@Test public void setAttributesFromMessage() { String sessionId = "session1"; ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(); SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(); headerAccessor.setSessionId(sessionId); headerAccessor.setSessionAttributes(map); Message<?> message = MessageBuilder.createMessage("", headerAccessor.getMessageHeaders()); SimpAttributesContextHolder.setAttributesFromMessage(message); SimpAttributes attrs = SimpAttributesContextHolder.getAttributes(); assertThat(attrs, notNullValue()); assertThat(attrs.getSessionId(), is(sessionId)); attrs.setAttribute("name1", "value1"); assertThat(map.get("name1"), is("value1")); }
/** * Extract the SiMP session attributes from the given message, wrap them in * a {@link SimpAttributes} instance and bind it to the current thread. * @param message the message to extract session attributes from */ public static void setAttributesFromMessage(Message<?> message) { setAttributes(SimpAttributes.fromMessage(message)); }
@Test public void getSessionMutexExplicit() { Object mutex = new Object(); this.simpAttributes.setAttribute(SimpAttributes.SESSION_MUTEX_NAME, mutex); assertThat(this.simpAttributes.getSessionMutex(), sameInstance(mutex)); }
@Override public String getConversationId() { return SimpAttributesContextHolder.currentAttributes().getSessionId(); }
@Override public void registerDestructionCallback(String name, Runnable callback) { SimpAttributesContextHolder.currentAttributes().registerDestructionCallback(name, callback); }
private void removeDestructionCallback(String name) { synchronized (getSessionMutex()) { this.attributes.remove(DESTRUCTION_CALLBACK_NAME_PREFIX + name); } }
@Override public boolean send(Message<?> message) { SimpAttributes simpAttributes = SimpAttributesContextHolder.currentAttributes(); assertThat(simpAttributes.getAttribute("name"), is("value")); return true; } @Override
@Test public void getAttributeNames() { this.simpAttributes.setAttribute("name1", "value1"); this.simpAttributes.setAttribute("name2", "value1"); this.simpAttributes.setAttribute("name3", "value1"); assertThat(this.simpAttributes.getAttributeNames(), arrayContainingInAnyOrder("name1", "name2", "name3")); }
@Test public void removeDestructionCallback() { Runnable callback1 = Mockito.mock(Runnable.class); Runnable callback2 = Mockito.mock(Runnable.class); this.simpAttributes.registerDestructionCallback("name1", callback1); this.simpAttributes.registerDestructionCallback("name2", callback2); assertThat(this.simpAttributes.getAttributeNames().length, is(2)); }
@Test public void get() { this.simpAttributes.setAttribute("name", "value"); Object actual = this.scope.get("name", this.objectFactory); assertThat(actual, is("value")); }