Browse Source
This change adds support for a custom "websocket" scope. WebSocket-scoped beans may be injected into controllers with message handling methods as well as channel interceptor registered on the "inboundClientChannel". Issue: SPR-11305pull/540/head
18 changed files with 1090 additions and 26 deletions
@ -0,0 +1,209 @@
@@ -0,0 +1,209 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.MessageHeaders; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* A wrapper class for access to attributes associated with a SiMP session |
||||
* (e.g. WebSocket session). |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class SimpAttributes { |
||||
|
||||
private static Log logger = LogFactory.getLog(SimpAttributes.class); |
||||
|
||||
private static final String className = SimpAttributes.class.getName(); |
||||
|
||||
/** Key for the mutex session attribute */ |
||||
public static final String SESSION_MUTEX_NAME = className + ".MUTEX"; |
||||
|
||||
/** Key set after the session is completed */ |
||||
public static final String SESSION_COMPLETED_NAME = className + ".COMPLETED"; |
||||
|
||||
/** Prefix for the name of session attributes used to store destruction callbacks. */ |
||||
public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = className + ".DESTRUCTION_CALLBACK."; |
||||
|
||||
|
||||
private final String sessionId; |
||||
|
||||
private final Map<String, Object> attributes; |
||||
|
||||
|
||||
/** |
||||
* Constructor wrapping the given session attributes map. |
||||
* |
||||
* @param sessionId the id of the associated session |
||||
* @param attributes the attributes |
||||
*/ |
||||
public SimpAttributes(String sessionId, Map<String, Object> attributes) { |
||||
Assert.notNull(sessionId, "'sessionId' is required"); |
||||
Assert.notNull(attributes, "'attributes' is required"); |
||||
this.sessionId = sessionId; |
||||
this.attributes = attributes; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Extract the SiMP session attributes from the given message, 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); |
||||
MessageHeaders headers = message.getHeaders(); |
||||
String sessionId = SimpMessageHeaderAccessor.getSessionId(headers); |
||||
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor.getSessionAttributes(headers); |
||||
if (sessionId == null || sessionAttributes == null) { |
||||
throw new IllegalStateException( |
||||
"Message does not contain SiMP session id or attributes: " + message); |
||||
} |
||||
return new SimpAttributes(sessionId, sessionAttributes); |
||||
} |
||||
|
||||
/** |
||||
* Return the value for the attribute of the given name, if any. |
||||
* |
||||
* @param name the name of the attribute |
||||
* @return the current attribute value, or {@code null} if not found |
||||
*/ |
||||
public Object getAttribute(String name) { |
||||
return this.attributes.get(name); |
||||
} |
||||
|
||||
/** |
||||
* Set the value with the given name replacing an existing value (if any). |
||||
* |
||||
* @param name the name of the attribute |
||||
* @param value the value for the attribute |
||||
*/ |
||||
public void setAttribute(String name, Object value) { |
||||
this.attributes.put(name, value); |
||||
} |
||||
|
||||
/** |
||||
* Remove the attribute of the given name, if it exists. |
||||
* |
||||
* <p>Also removes the registered destruction callback for the specified |
||||
* attribute, if any. However it <i>does not</i> execute</i> the callback. |
||||
* It is assumed the removed object will continue to be used and destroyed |
||||
* independently at the appropriate time. |
||||
* |
||||
* @param name the name of the attribute |
||||
*/ |
||||
public void removeAttribute(String name) { |
||||
this.attributes.remove(name); |
||||
removeDestructionCallback(name); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the names of all attributes. |
||||
* |
||||
* @return the attribute names as String array, never {@code null} |
||||
*/ |
||||
public String[] getAttributeNames() { |
||||
return StringUtils.toStringArray(this.attributes.keySet()); |
||||
} |
||||
|
||||
/** |
||||
* 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); |
||||
} |
||||
} |
||||
|
||||
private void removeDestructionCallback(String name) { |
||||
synchronized (getSessionMutex()) { |
||||
this.attributes.remove(DESTRUCTION_CALLBACK_NAME_PREFIX + name); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return an id for the associated session. |
||||
* |
||||
* @return the session id as String (never {@code null}) |
||||
*/ |
||||
public String getSessionId() { |
||||
return this.sessionId; |
||||
} |
||||
|
||||
/** |
||||
* Expose the object to synchronize on for the underlying session. |
||||
* |
||||
* @return the session mutex to use (never {@code null}) |
||||
*/ |
||||
public Object getSessionMutex() { |
||||
Object mutex = this.attributes.get(SESSION_MUTEX_NAME); |
||||
if (mutex == null) { |
||||
mutex = this.attributes; |
||||
} |
||||
return mutex; |
||||
} |
||||
|
||||
/** |
||||
* Whether the {@link #sessionCompleted()} was already invoked. |
||||
*/ |
||||
public boolean isSessionCompleted() { |
||||
return (this.attributes.get(SESSION_COMPLETED_NAME) != null); |
||||
} |
||||
|
||||
/** |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void executeDestructionCallbacks() { |
||||
for (Map.Entry<String, Object> entry : this.attributes.entrySet()) { |
||||
if (entry.getKey().startsWith(DESTRUCTION_CALLBACK_NAME_PREFIX)) { |
||||
try { |
||||
((Runnable) entry.getValue()).run(); |
||||
} |
||||
catch (Throwable t) { |
||||
if (logger.isErrorEnabled()) { |
||||
logger.error("Uncaught error in session attribute destruction callback", t); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.springframework.core.NamedThreadLocal; |
||||
import org.springframework.messaging.Message; |
||||
|
||||
|
||||
/** |
||||
* Holder class to expose SiMP attributes associated with a session (e.g. WebSocket) |
||||
* in the form of a thread-bound {@link SimpAttributes} object. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public abstract class SimpAttributesContextHolder { |
||||
|
||||
private static final ThreadLocal<SimpAttributes> attributesHolder = |
||||
new NamedThreadLocal<SimpAttributes>("SiMP session attributes"); |
||||
|
||||
|
||||
/** |
||||
* Reset the SimpAttributes for the current thread. |
||||
*/ |
||||
public static void resetAttributes() { |
||||
attributesHolder.remove(); |
||||
} |
||||
|
||||
/** |
||||
* Bind the given SimpAttributes to the current thread, |
||||
* |
||||
* @param attributes the RequestAttributes to expose |
||||
*/ |
||||
public static void setAttributes(SimpAttributes attributes) { |
||||
if (attributes != null) { |
||||
attributesHolder.set(attributes); |
||||
} |
||||
else { |
||||
resetAttributes(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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)); |
||||
} |
||||
|
||||
/** |
||||
* Return the SimpAttributes currently bound to the thread. |
||||
* |
||||
* @return the attributes or {@code null} if not bound |
||||
*/ |
||||
public static SimpAttributes getAttributes() { |
||||
return attributesHolder.get(); |
||||
} |
||||
|
||||
/** |
||||
* Return the SimpAttributes currently bound to the thread or raise an |
||||
* {@link java.lang.IllegalStateException} if none are bound.. |
||||
* |
||||
* @return the attributes, never {@code null} |
||||
* @throws java.lang.IllegalStateException if attributes are not bound |
||||
*/ |
||||
public static SimpAttributes currentAttributes() throws IllegalStateException { |
||||
SimpAttributes attributes = getAttributes(); |
||||
if (attributes == null) { |
||||
throw new IllegalStateException("No thread-bound SimpAttributes found. " + |
||||
"Your code is probably not processing a client message and executing in " + |
||||
"message-handling methods invoked by the SimpAnnotationMethodMessageHandler?"); |
||||
} |
||||
return attributes; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.springframework.beans.factory.ObjectFactory; |
||||
import org.springframework.beans.factory.config.Scope; |
||||
|
||||
/** |
||||
* A {@link Scope} implementation exposing the attributes of a SiMP session |
||||
* (e.g. WebSocket session). |
||||
* |
||||
* <p>Relies on a thread-bound {@link SimpAttributes} instance exported by |
||||
* {@link org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler |
||||
* SimpAnnotationMethodMessageHandler}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class SimpSessionScope implements Scope { |
||||
|
||||
|
||||
@Override |
||||
public Object get(String name, ObjectFactory<?> objectFactory) { |
||||
SimpAttributes simpAttributes = SimpAttributesContextHolder.currentAttributes(); |
||||
Object value = simpAttributes.getAttribute(name); |
||||
if (value != null) { |
||||
return value; |
||||
} |
||||
synchronized (simpAttributes.getSessionMutex()) { |
||||
value = simpAttributes.getAttribute(name); |
||||
if (value == null) { |
||||
value = objectFactory.getObject(); |
||||
simpAttributes.setAttribute(name, value); |
||||
} |
||||
return value; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
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; |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void registerDestructionCallback(String name, Runnable callback) { |
||||
SimpAttributesContextHolder.currentAttributes().registerDestructionCallback(name, callback); |
||||
} |
||||
|
||||
@Override |
||||
public Object resolveContextualObject(String key) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String getConversationId() { |
||||
return SimpAttributesContextHolder.currentAttributes().getSessionId(); |
||||
} |
||||
} |
||||
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.support.GenericMessage; |
||||
import org.springframework.messaging.support.MessageBuilder; |
||||
|
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import static org.hamcrest.MatcherAssert.*; |
||||
import static org.hamcrest.Matchers.is; |
||||
import static org.hamcrest.Matchers.notNullValue; |
||||
import static org.hamcrest.Matchers.nullValue; |
||||
import static org.hamcrest.Matchers.sameInstance; |
||||
import static org.hamcrest.Matchers.startsWith; |
||||
|
||||
/** |
||||
* Unit tests for |
||||
* {@link org.springframework.messaging.simp.SimpAttributesContextHolder}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class SimpAttributesContextHolderTests { |
||||
|
||||
private SimpAttributes simpAttributes; |
||||
|
||||
@Rule |
||||
public ExpectedException thrown = ExpectedException.none(); |
||||
|
||||
|
||||
@Before |
||||
public void setUp() { |
||||
Map<String, Object> map = new ConcurrentHashMap<>(); |
||||
this.simpAttributes = new SimpAttributes("session1", map); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() { |
||||
SimpAttributesContextHolder.resetAttributes(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void resetAttributes() { |
||||
SimpAttributesContextHolder.setAttributes(this.simpAttributes); |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), sameInstance(this.simpAttributes)); |
||||
|
||||
SimpAttributesContextHolder.resetAttributes(); |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void getAttributes() { |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), nullValue()); |
||||
|
||||
SimpAttributesContextHolder.setAttributes(this.simpAttributes); |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), sameInstance(this.simpAttributes)); |
||||
} |
||||
|
||||
@Test |
||||
public void setAttributes() { |
||||
SimpAttributesContextHolder.setAttributes(this.simpAttributes); |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), sameInstance(this.simpAttributes)); |
||||
|
||||
SimpAttributesContextHolder.setAttributes(null); |
||||
assertThat(SimpAttributesContextHolder.getAttributes(), nullValue()); |
||||
} |
||||
|
||||
@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")); |
||||
} |
||||
|
||||
@Test |
||||
public void setAttributesFromMessageWithMissingHeaders() { |
||||
this.thrown.expect(IllegalStateException.class); |
||||
this.thrown.expectMessage(startsWith("Message does not contain SiMP session id or attributes")); |
||||
SimpAttributesContextHolder.setAttributesFromMessage(new GenericMessage<Object>("")); |
||||
} |
||||
|
||||
@Test |
||||
public void currentAttributes() { |
||||
SimpAttributesContextHolder.setAttributes(this.simpAttributes); |
||||
assertThat(SimpAttributesContextHolder.currentAttributes(), sameInstance(this.simpAttributes)); |
||||
} |
||||
|
||||
@Test |
||||
public void currentAttributesNone() { |
||||
this.thrown.expect(IllegalStateException.class); |
||||
this.thrown.expectMessage(startsWith("No thread-bound SimpAttributes found")); |
||||
SimpAttributesContextHolder.currentAttributes(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.junit.rules.ExpectedException; |
||||
import org.mockito.Mockito; |
||||
|
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import static org.mockito.Mockito.*; |
||||
import static org.junit.Assert.assertThat; |
||||
import static org.hamcrest.Matchers.*; |
||||
|
||||
/** |
||||
* Unit tests for |
||||
* {@link org.springframework.messaging.simp.SimpAttributes}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class SimpAttributesTests { |
||||
|
||||
private SimpAttributes simpAttributes; |
||||
|
||||
private Map<String, Object> map; |
||||
|
||||
@Rule |
||||
public ExpectedException thrown = ExpectedException.none(); |
||||
|
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.map = new ConcurrentHashMap<>(); |
||||
this.simpAttributes = new SimpAttributes("session1", this.map); |
||||
} |
||||
|
||||
|
||||
@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 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 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 registerDestructionCallbackAfterSessionCompleted() { |
||||
this.simpAttributes.sessionCompleted(); |
||||
this.thrown.expect(IllegalStateException.class); |
||||
this.thrown.expectMessage(containsString("already completed")); |
||||
this.simpAttributes.registerDestructionCallback("name1", Mockito.mock(Runnable.class)); |
||||
} |
||||
|
||||
@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 getSessionMutex() { |
||||
assertThat(this.simpAttributes.getSessionMutex(), sameInstance(this.map)); |
||||
} |
||||
|
||||
@Test |
||||
public void getSessionMutexExplicit() { |
||||
Object mutex = new Object(); |
||||
this.simpAttributes.setAttribute(SimpAttributes.SESSION_MUTEX_NAME, mutex); |
||||
|
||||
assertThat(this.simpAttributes.getSessionMutex(), sameInstance(mutex)); |
||||
} |
||||
|
||||
@Test |
||||
public void sessionCompleted() { |
||||
Runnable callback1 = Mockito.mock(Runnable.class); |
||||
Runnable callback2 = Mockito.mock(Runnable.class); |
||||
this.simpAttributes.registerDestructionCallback("name1", callback1); |
||||
this.simpAttributes.registerDestructionCallback("name2", callback2); |
||||
|
||||
this.simpAttributes.sessionCompleted(); |
||||
|
||||
verify(callback1, times(1)).run(); |
||||
verify(callback2, 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(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.messaging.simp; |
||||
|
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.ObjectFactory; |
||||
|
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import static org.junit.Assert.assertThat; |
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
/** |
||||
* Unit tests for {@link org.springframework.messaging.simp.SimpSessionScope}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class SimpSessionScopeTests { |
||||
|
||||
private SimpSessionScope scope; |
||||
|
||||
private ObjectFactory objectFactory; |
||||
|
||||
private SimpAttributes simpAttributes; |
||||
|
||||
|
||||
@Before |
||||
public void setUp() { |
||||
this.scope = new SimpSessionScope(); |
||||
this.objectFactory = Mockito.mock(ObjectFactory.class); |
||||
this.simpAttributes = new SimpAttributes("session1", new ConcurrentHashMap<>()); |
||||
SimpAttributesContextHolder.setAttributes(this.simpAttributes); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() { |
||||
SimpAttributesContextHolder.resetAttributes(); |
||||
} |
||||
|
||||
@Test |
||||
public void get() { |
||||
this.simpAttributes.setAttribute("name", "value"); |
||||
Object actual = this.scope.get("name", this.objectFactory); |
||||
|
||||
assertThat(actual, is("value")); |
||||
} |
||||
|
||||
@Test |
||||
public void getWithObjectFactory() { |
||||
when(this.objectFactory.getObject()).thenReturn("value"); |
||||
Object actual = this.scope.get("name", this.objectFactory); |
||||
|
||||
assertThat(actual, is("value")); |
||||
assertThat(this.simpAttributes.getAttribute("name"), is("value")); |
||||
} |
||||
|
||||
@Test |
||||
public void remove() { |
||||
this.simpAttributes.setAttribute("name", "value"); |
||||
|
||||
Object removed = this.scope.remove("name"); |
||||
assertThat(removed, is("value")); |
||||
assertThat(this.simpAttributes.getAttribute("name"), nullValue()); |
||||
|
||||
removed = this.scope.remove("name"); |
||||
assertThat(removed, nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void registerDestructionCallback() { |
||||
Runnable runnable = Mockito.mock(Runnable.class); |
||||
this.scope.registerDestructionCallback("name", runnable); |
||||
|
||||
this.simpAttributes.sessionCompleted(); |
||||
verify(runnable, times(1)).run(); |
||||
} |
||||
|
||||
@Test |
||||
public void getSessionId() { |
||||
assertThat(this.scope.getConversationId(), is("session1")); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.web.socket.config.annotation; |
||||
|
||||
import org.springframework.beans.factory.config.CustomScopeConfigurer; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.messaging.simp.SimpSessionScope; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
/** |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
public class WebSocketScopeConfiguration { |
||||
|
||||
@Bean |
||||
public CustomScopeConfigurer webSocketScopeConfigurer() { |
||||
CustomScopeConfigurer configurer = new CustomScopeConfigurer(); |
||||
configurer.setScopes(Collections.<String, Object>singletonMap("websocket", new SimpSessionScope())); |
||||
return configurer; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue