You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2014/09/17 14:45:00 UTC

git commit: WICKET-5701 WebSocketRequestHandler is not set as a scheduled and thus RequestCycle#find(AjaxRequestTarget.class) doesn't work

Repository: wicket
Updated Branches:
  refs/heads/master 8f4a13980 -> 087c0a26c


WICKET-5701 WebSocketRequestHandler is not set as a scheduled and thus RequestCycle#find(AjaxRequestTarget.class) doesn't work

(cherry picked from commit 482c595f5c1b2ee5032140ed0ede79d9f48af26f)


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/087c0a26
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/087c0a26
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/087c0a26

Branch: refs/heads/master
Commit: 087c0a26c90f433748c3132b463a640c41cbb0f2
Parents: 8f4a139
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Wed Sep 17 15:37:21 2014 +0300
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Wed Sep 17 15:44:34 2014 +0300

----------------------------------------------------------------------
 .../ws/api/AbstractWebSocketProcessor.java      |  95 +++++----------
 .../api/WebSocketMessageBroadcastHandler.java   | 104 ++++++++++++++++
 .../protocol/ws/api/WebSocketRequestMapper.java |  74 ++++++++++++
 ...WebSocketTesterRequestCycleListenerTest.java | 120 +++++++++++++++++++
 4 files changed, 326 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/087c0a26/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java
index 9eb3d5d..f7428ea 100644
--- a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java
@@ -23,7 +23,6 @@ import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.Page;
 import org.apache.wicket.Session;
 import org.apache.wicket.ThreadContext;
-import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.markup.IMarkupResourceStreamProvider;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.page.IPageManager;
@@ -46,17 +45,15 @@ import org.apache.wicket.protocol.ws.api.registry.IKey;
 import org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionRegistry;
 import org.apache.wicket.protocol.ws.api.registry.PageIdKey;
 import org.apache.wicket.protocol.ws.api.registry.ResourceNameKey;
+import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.Url;
+import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.cycle.RequestCycleContext;
 import org.apache.wicket.request.http.WebRequest;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.request.resource.SharedResourceReference;
 import org.apache.wicket.session.ISessionStore;
 import org.apache.wicket.util.lang.Args;
 import org.apache.wicket.util.lang.Checks;
-import org.apache.wicket.util.lang.Classes;
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.StringResourceStream;
 import org.apache.wicket.util.string.Strings;
@@ -76,7 +73,7 @@ public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor
 	/**
 	 * A pageId indicating that the endpoint is WebSocketResource
 	 */
-	private static final int NO_PAGE_ID = -1;
+	static final int NO_PAGE_ID = -1;
 
 	private final WebRequest webRequest;
 	private final int pageId;
@@ -187,20 +184,9 @@ public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor
 			WebSocketResponse webResponse = new WebSocketResponse(connection);
 			try
 			{
-				RequestCycle requestCycle;
-				if (oldRequestCycle == null || message instanceof IWebSocketPushMessage)
-				{
-					RequestCycleContext context = new RequestCycleContext(webRequest, webResponse,
-							application.getRootRequestMapper(), application.getExceptionMapperProvider().get());
-
-					requestCycle = application.getRequestCycleProvider().get(context);
-					requestCycle.getUrlRenderer().setBaseUrl(baseUrl);
-					ThreadContext.setRequestCycle(requestCycle);
-				}
-				else
-				{
-					requestCycle = oldRequestCycle;
-				}
+				WebSocketRequestMapper requestMapper = new WebSocketRequestMapper(application.getRootRequestMapper());
+				RequestCycle requestCycle = createRequestCycle(requestMapper, webResponse);
+				ThreadContext.setRequestCycle(requestCycle);
 
 				ThreadContext.setApplication(application);
 
@@ -217,25 +203,20 @@ public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor
 				}
 
 				IPageManager pageManager = session.getPageManager();
-				try
-				{
-					Page page = getPage(pageManager);
+				Page page = getPage(pageManager);
 
-					WebSocketRequestHandler requestHandler = new WebSocketRequestHandler(page, connection);
+				WebSocketRequestHandler requestHandler = new WebSocketRequestHandler(page, connection);
 
-					WebSocketPayload payload = createEventPayload(message, requestHandler);
+				WebSocketPayload payload = createEventPayload(message, requestHandler);
 
-					sendPayload(payload, page);
-
-					if (!(message instanceof ConnectedMessage || message instanceof ClosedMessage))
-					{
-						requestHandler.respond(requestCycle);
-					}
-				}
-				finally
+				if (!(message instanceof ConnectedMessage || message instanceof ClosedMessage))
 				{
-					pageManager.commitRequest();
+					requestCycle.scheduleRequestHandlerAfterCurrent(requestHandler);
 				}
+
+				IRequestHandler broadcastingHandler = new WebSocketMessageBroadcastHandler(pageId, resourceName, payload);
+				requestMapper.setHandler(broadcastingHandler);
+				requestCycle.processRequestAndDetach();
 			}
 			catch (Exception x)
 			{
@@ -261,46 +242,26 @@ public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor
 		}
 	}
 
-	/**
-	 * Sends the payload either to the page (and its WebSocketBehavior)
-	 * or to the WebSocketResource with name {@linkplain #resourceName}
-	 *
-	 * @param payload
-	 *          The payload with the web socket message
-	 * @param page
-	 *          The page that owns the WebSocketBehavior, in case of behavior usage
-	 */
-	private void sendPayload(final WebSocketPayload payload, final Page page)
+	private RequestCycle createRequestCycle(WebSocketRequestMapper requestMapper, WebSocketResponse webResponse)
 	{
-		final Runnable action = new Runnable()
+		RequestCycleContext context = new RequestCycleContext(webRequest, webResponse,
+				requestMapper, application.getExceptionMapperProvider().get());
+
+		RequestCycle requestCycle = application.getRequestCycleProvider().get(context);
+		requestCycle.getListeners().add(application.getRequestCycleListeners());
+		requestCycle.getListeners().add(new AbstractRequestCycleListener()
 		{
 			@Override
-			public void run()
+			public void onDetach(final RequestCycle requestCycle)
 			{
-				if (pageId != NO_PAGE_ID)
-				{
-					page.send(application, Broadcast.BREADTH, payload);
-				} else
+				if (Session.exists())
 				{
-					ResourceReference reference = new SharedResourceReference(resourceName);
-					IResource resource = reference.getResource();
-					if (resource instanceof WebSocketResource)
-					{
-						WebSocketResource wsResource = (WebSocketResource) resource;
-						wsResource.onPayload(payload);
-					} else
-					{
-						throw new IllegalStateException(
-								String.format("Shared resource with name '%s' is not a %s but %s",
-										resourceName, WebSocketResource.class.getSimpleName(),
-										Classes.name(resource.getClass())));
-					}
+					Session.get().getPageManager().commitRequest();
 				}
 			}
-		};
-
-		WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(application);
-		webSocketSettings.getSendPayloadExecutor().run(action);
+		});
+		requestCycle.getUrlRenderer().setBaseUrl(baseUrl);
+		return requestCycle;
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/wicket/blob/087c0a26/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketMessageBroadcastHandler.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketMessageBroadcastHandler.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketMessageBroadcastHandler.java
new file mode 100644
index 0000000..0529fe3
--- /dev/null
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketMessageBroadcastHandler.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.protocol.ws.api;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Page;
+import org.apache.wicket.Session;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPayload;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.resource.IResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.request.resource.SharedResourceReference;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Classes;
+
+/**
+ * An {@link org.apache.wicket.request.IRequestHandler} that broadcasts the payload to the
+ * page/resource
+ */
+public class WebSocketMessageBroadcastHandler implements IRequestHandler
+{
+	private final int pageId;
+	private final String resourceName;
+	private final WebSocketPayload<?> payload;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param pageId
+	 *          The id of the page if {@link org.apache.wicket.protocol.ws.api.WebSocketBehavior}
+	 *          or {@value org.apache.wicket.protocol.ws.api.AbstractWebSocketProcessor#NO_PAGE_ID} if using a resource
+	 * @param resourceName
+	 *          The name of the shared {@link org.apache.wicket.protocol.ws.api.WebSocketResource}
+	 * @param payload
+	 *          The payload to broadcast
+	 */
+	WebSocketMessageBroadcastHandler(int pageId, String resourceName, WebSocketPayload<?> payload)
+	{
+		this.pageId = pageId;
+		this.resourceName = resourceName;
+		this.payload = Args.notNull(payload, "payload");
+	}
+
+	@Override
+	public void respond(IRequestCycle requestCycle)
+	{
+		final Application application = Application.get();
+
+		final Runnable action = new Runnable()
+		{
+			@Override
+			public void run()
+			{
+				if (pageId != AbstractWebSocketProcessor.NO_PAGE_ID)
+				{
+					Page page = (Page) Session.get().getPageManager().getPage(pageId);
+					page.send(application, Broadcast.BREADTH, payload);
+				}
+				else
+				{
+					ResourceReference reference = new SharedResourceReference(resourceName);
+					IResource resource = reference.getResource();
+					if (resource instanceof WebSocketResource)
+					{
+						WebSocketResource wsResource = (WebSocketResource) resource;
+						wsResource.onPayload(payload);
+					}
+					else
+					{
+						throw new IllegalStateException(
+								String.format("Shared resource with name '%s' is not a %s but %s",
+										resourceName, WebSocketResource.class.getSimpleName(),
+										Classes.name(resource.getClass())));
+					}
+				}
+			}
+		};
+
+		WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(application);
+		webSocketSettings.getSendPayloadExecutor().run(action);
+	}
+
+	@Override
+	public void detach(IRequestCycle requestCycle)
+	{
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/087c0a26/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestMapper.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestMapper.java b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestMapper.java
new file mode 100644
index 0000000..9899b04
--- /dev/null
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequestMapper.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.protocol.ws.api;
+
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.IRequestMapper;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.Url;
+
+/**
+ * An {@link org.apache.wicket.request.IRequestMapper} that is used to set a custom
+ * {@link org.apache.wicket.request.IRequestHandler} that broadcasts the
+ * {@link org.apache.wicket.protocol.ws.api.event.WebSocketPayload}
+ */
+class WebSocketRequestMapper implements IRequestMapper
+{
+	private final IRequestMapper delegate;
+
+	private IRequestHandler handler;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param delegate
+	 *          The application root request mapper to delegate Url creation etc.
+	 */
+	public WebSocketRequestMapper(IRequestMapper delegate)
+	{
+		this.delegate = delegate;
+	}
+
+	@Override
+	public IRequestHandler mapRequest(Request request)
+	{
+		return handler;
+	}
+
+	@Override
+	public int getCompatibilityScore(Request request)
+	{
+		return delegate.getCompatibilityScore(request);
+	}
+
+	@Override
+	public Url mapHandler(IRequestHandler requestHandler)
+	{
+		return delegate.mapHandler(requestHandler);
+	}
+
+	/**
+	 * Sets the custom request handler
+	 *
+	 * @param handler
+	 *          The request handler that broadcasts the web socket payload
+	 */
+	public void setHandler(IRequestHandler handler)
+	{
+		this.handler = handler;
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/087c0a26/wicket-native-websocket/wicket-native-websocket-core/src/test/java/org/apache/wicket/protocol/ws/util/tester/WebSocketTesterRequestCycleListenerTest.java
----------------------------------------------------------------------
diff --git a/wicket-native-websocket/wicket-native-websocket-core/src/test/java/org/apache/wicket/protocol/ws/util/tester/WebSocketTesterRequestCycleListenerTest.java b/wicket-native-websocket/wicket-native-websocket-core/src/test/java/org/apache/wicket/protocol/ws/util/tester/WebSocketTesterRequestCycleListenerTest.java
new file mode 100644
index 0000000..0b5b95d
--- /dev/null
+++ b/wicket-native-websocket/wicket-native-websocket-core/src/test/java/org/apache/wicket/protocol/ws/util/tester/WebSocketTesterRequestCycleListenerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.protocol.ws.util.tester;
+
+import static org.hamcrest.CoreMatchers.is;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for WebSocketTester.
+ * Uses WebSocketBehavior.
+ *
+ * @since 6.18.0
+ */
+public class WebSocketTesterRequestCycleListenerTest extends Assert
+{
+	private final AtomicBoolean beginRequestCalled = new AtomicBoolean(false);
+	private final AtomicBoolean endRequestCalled = new AtomicBoolean(false);
+	private final AtomicBoolean detachCalled = new AtomicBoolean(false);
+
+	private WicketTester tester;
+
+	@Before
+	public void before()
+	{
+		tester = new WicketTester();
+		tester.getApplication().getRequestCycleListeners().add(new AbstractRequestCycleListener()
+		{
+			@Override
+			public void onBeginRequest(RequestCycle cycle)
+			{
+				beginRequestCalled.set(true);
+			}
+
+			@Override
+			public void onEndRequest(RequestCycle cycle)
+			{
+				endRequestCalled.set(true);
+			}
+
+			@Override
+			public void onDetach(RequestCycle cycle)
+			{
+				detachCalled.set(true);
+			}
+		});
+	}
+
+	@After
+	public void after()
+	{
+		tester.destroy();
+	}
+
+	/**
+	 * A simple test that sends and receives a text message.
+	 * The page asserts that it received the correct message and then
+	 * pushed back the same message but capitalized.
+	 */
+	@Test
+	public void verifyRequestCycleListeners()
+	{
+		final String expectedMessage = "some message";
+
+		WebSocketBehaviorTestPage page = new WebSocketBehaviorTestPage(expectedMessage);
+		tester.startPage(page);
+
+		// reset the variables after starting the page (no WebSocket related request)
+		beginRequestCalled.set(false);
+		endRequestCalled.set(false);
+		detachCalled.set(false);
+
+		// broadcasts WebSocket.ConnectedMessage and notifies the listeners
+		WebSocketTester webSocketTester = new WebSocketTester(tester, page) {
+			@Override
+			protected void onOutMessage(String message)
+			{
+				assertEquals(Strings.capitalize(expectedMessage), message);
+			}
+		};
+
+		// assert and reset
+		assertThat(beginRequestCalled.compareAndSet(true, false), is(true));
+		assertThat(endRequestCalled.compareAndSet(true, false), is(true));
+		assertThat(detachCalled.compareAndSet(true, false), is(true));
+
+		// broadcasts WebSocket.TextMessage and notifies the listeners
+		webSocketTester.sendMessage(expectedMessage);
+
+		assertThat(beginRequestCalled.get(), is(true));
+		assertThat(endRequestCalled.get(), is(true));
+		assertThat(detachCalled.get(), is(true));
+
+		webSocketTester.destroy();
+	}
+
+}