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 <[email protected]> Authored: Wed Sep 17 15:37:21 2014 +0300 Committer: Martin Tzvetanov Grigorov <[email protected]> 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(); + } + +}
