├── .gitignore ├── workers ├── readme.md ├── src │ ├── main │ │ └── java │ │ │ ├── com │ │ │ └── colinalworth │ │ │ │ └── gwt │ │ │ │ └── worker │ │ │ │ ├── client │ │ │ │ ├── Endpoint.java │ │ │ │ ├── worker │ │ │ │ │ ├── Worker.java │ │ │ │ │ ├── SharedWorker.java │ │ │ │ │ ├── MessageEvent.java │ │ │ │ │ └── MessagePort.java │ │ │ │ ├── pako │ │ │ │ │ ├── Deflate.java │ │ │ │ │ └── Inflate.java │ │ │ │ ├── WorkerFactory.java │ │ │ │ └── impl │ │ │ │ │ ├── RemoteCallbackInvocation.java │ │ │ │ │ ├── RemoteCallbackInvocation_CustomFieldSerializer.java │ │ │ │ │ ├── AbstractWorkerFactoryImpl.java │ │ │ │ │ ├── RemoteInvocation.java │ │ │ │ │ ├── RemoteInvocation_CustomFieldSerializer.java │ │ │ │ │ ├── StreamReader.java │ │ │ │ │ ├── AbstractWorkerEndpointImpl.java │ │ │ │ │ └── StreamWriter.java │ │ │ │ ├── RpcToWorkers.gwt.xml │ │ │ │ └── rebind │ │ │ │ └── EndpointFactoryGenerator.java │ │ │ └── prj │ │ │ └── gwtwwlinker │ │ │ └── workerlinker │ │ │ ├── WorkerLinker.gwt.xml │ │ │ └── code │ │ │ └── WorkerLinker.java │ └── test │ │ └── java │ │ └── com │ │ └── colinalworth │ │ └── gwt │ │ └── worker │ │ └── client │ │ └── BrokenGwtTestEndpoint.java └── pom.xml ├── workers-sample ├── src │ └── main │ │ ├── webapp │ │ ├── WEB-INF │ │ │ └── web.xml │ │ ├── simpleworker.html │ │ └── sharedchat.html │ │ └── java │ │ ├── simpleworker │ │ ├── common │ │ │ ├── Common.gwt.xml │ │ │ └── client │ │ │ │ ├── MyHost.java │ │ │ │ └── MyWorker.java │ │ ├── ui │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── AppUI.gwt.xml │ │ │ └── client │ │ │ │ └── AppUI.java │ │ └── worker │ │ │ ├── AppWorker.gwt.xml │ │ │ └── client │ │ │ └── AppWorker.java │ │ └── sharedchat │ │ ├── common │ │ ├── Common.gwt.xml │ │ ├── shared │ │ │ ├── ChatEvent.java │ │ │ ├── ChatJoin.java │ │ │ ├── ChatLeave.java │ │ │ ├── ChatServer.java │ │ │ ├── ChatClient.java │ │ │ └── ChatMessage.java │ │ └── client │ │ │ ├── ChatWorker.java │ │ │ └── ChatPage.java │ │ ├── ui │ │ ├── AppUI.gwt.xml │ │ └── client │ │ │ ├── ChatClientWidget.java │ │ │ └── AppUI.java │ │ ├── worker │ │ ├── AppWorker.gwt.xml │ │ └── client │ │ │ └── AppWorker.java │ │ └── server │ │ └── ChatServerImpl.java └── pom.xml ├── javaee-websocket-gwt-rpc-sample ├── src │ └── main │ │ ├── webapp │ │ ├── WEB-INF │ │ │ └── web.xml │ │ └── index.html │ │ └── java │ │ └── samples │ │ └── easychatroom2 │ │ ├── ChatSample.gwt.xml │ │ ├── shared │ │ ├── ChatServer.java │ │ └── ChatClient.java │ │ ├── client │ │ ├── ChatClientWidget.java │ │ └── SampleEntryPoint.java │ │ └── server │ │ └── ChatServerImpl.java └── pom.xml ├── rpc-client-common ├── src │ └── main │ │ └── java │ │ └── com │ │ └── colinalworth │ │ └── gwt │ │ └── websockets │ │ ├── RpcOverWebSockets.gwt.xml │ │ ├── shared │ │ ├── impl │ │ │ ├── DummyRemoteService.java │ │ │ ├── DummyRemoteServiceAsync.java │ │ │ ├── ClientCallbackInvocation.java │ │ │ ├── ServerCallbackInvocation.java │ │ │ ├── ClientCallbackInvocation_CustomFieldSerializer.java │ │ │ ├── ServerCallbackInvocation_CustomFieldSerializer.java │ │ │ ├── ClientInvocation.java │ │ │ ├── ServerInvocation.java │ │ │ ├── ServerInvocation_CustomFieldSerializer.java │ │ │ └── ClientInvocation_CustomFieldSerializer.java │ │ ├── Client.java │ │ └── Server.java │ │ ├── client │ │ ├── ConnectionOpenedEvent.java │ │ ├── ConnectionClosedEvent.java │ │ ├── impl │ │ │ ├── WebSocket.java │ │ │ └── ServerBuilderImpl.java │ │ ├── AbstractClientImpl.java │ │ └── ServerBuilder.java │ │ └── rebind │ │ ├── ServerBuilderGenerator.java │ │ └── ServerCreator.java └── pom.xml ├── javaee-websocket-gwt-rpc ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── colinalworth │ └── gwt │ └── websockets │ └── server │ ├── AbstractServerImpl.java │ └── RpcEndpoint.java ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | bin 3 | .project 4 | .classpath 5 | .settings 6 | *.iml 7 | dependency-reduced-pom.xml 8 | -------------------------------------------------------------------------------- /workers/readme.md: -------------------------------------------------------------------------------- 1 | Even more than the rest of this project, this module tends toward bleeding edge. Dev mode is simply not supported, 2 | and typed arrays are required to be fully functioning. It wouldn't be impossible to add dev mode support, but it 3 | might not be worth the time it would take to branch in cases where TypedArrays as not actually JSOs. 4 | 5 | Tests will appear to pass in dev mode, since the tests are all marked as `@DoNotRunWith(Platform.Devel)`. -------------------------------------------------------------------------------- /workers-sample/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/common/Common.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/Common.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/common/client/MyHost.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package simpleworker.common.client; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | 24 | /** 25 | * Created by colin on 2/10/16. 26 | */ 27 | public interface MyHost extends Endpoint { 28 | void pong(); 29 | } 30 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/ui/AppUI.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/ui/public/index.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | Web Worker RPC Sample 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/ui/AppUI.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /workers-sample/src/main/webapp/simpleworker.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | Web Worker RPC Sample 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import sharedchat.common.client.ChatPage; 23 | 24 | import java.io.Serializable; 25 | 26 | /** 27 | * Created by colin on 2/7/17. 28 | */ 29 | public interface ChatEvent extends Serializable { 30 | void handle(ChatPage chatPage); 31 | } 32 | -------------------------------------------------------------------------------- /workers-sample/src/main/webapp/sharedchat.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | Shared Worker and WebSocket RPC Sample 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/Endpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client; 21 | 22 | /** 23 | * Created by colin on 1/14/16. 24 | */ 25 | public interface Endpoint, REMOTE extends Endpoint> { 26 | 27 | void setRemote(REMOTE remote); 28 | REMOTE getRemote(); 29 | } 30 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/RpcToWorkers.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/client/ChatWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.client; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | 24 | /** 25 | * Created by colin on 2/6/17. 26 | */ 27 | public interface ChatWorker extends Endpoint { 28 | void login(String username); 29 | 30 | void logout(); 31 | 32 | void send(String message); 33 | } 34 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/ChatSample.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/common/client/MyWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package simpleworker.common.client; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | import com.google.gwt.core.client.Callback; 24 | 25 | /** 26 | * Created by colin on 2/10/16. 27 | */ 28 | public interface MyWorker extends Endpoint { 29 | void ping(); 30 | 31 | void split(String pattern, String input, Callback callback); 32 | } 33 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | Chat Sample 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/RpcOverWebSockets.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/DummyRemoteService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.RemoteService; 23 | 24 | /** 25 | * Dummy service interface used to convince RPC internals that a real RPC call is being made. 26 | * 27 | */ 28 | public interface DummyRemoteService extends RemoteService { 29 | ClientInvocation invoke(ServerInvocation serverInvocation); 30 | ClientCallbackInvocation callback(ServerCallbackInvocation serverCallbackInvocation); 31 | } 32 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/worker/Worker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.worker; 21 | 22 | import com.google.gwt.core.client.JavaScriptObject; 23 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 24 | import jsinterop.annotations.JsPackage; 25 | import jsinterop.annotations.JsType; 26 | 27 | /** 28 | * Created by colin on 1/18/16. 29 | */ 30 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 31 | public class Worker extends MessagePort { 32 | 33 | public Worker(String path) { 34 | 35 | } 36 | 37 | public native void terminate(); 38 | } 39 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/worker/SharedWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.worker; 21 | 22 | import jsinterop.annotations.JsPackage; 23 | import jsinterop.annotations.JsProperty; 24 | import jsinterop.annotations.JsType; 25 | 26 | /** 27 | * Created by colin on 2/10/16. 28 | */ 29 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 30 | public final class SharedWorker { 31 | 32 | public SharedWorker(String path) { 33 | 34 | } 35 | public SharedWorker(String path, String name) { 36 | 37 | } 38 | 39 | @JsProperty 40 | public native MessagePort getPort(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/client/ChatPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.client; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | import sharedchat.common.shared.ChatEvent; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Created by colin on 2/6/17. 29 | */ 30 | public interface ChatPage extends Endpoint { 31 | void init(String username, List events); 32 | 33 | void connected(); 34 | 35 | void disconnected(); 36 | 37 | void join(String username); 38 | 39 | void leave(String username); 40 | 41 | void say(String username, String text); 42 | } 43 | -------------------------------------------------------------------------------- /rpc-client-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gwt-websockets-parent 7 | com.colinalworth.gwt.websockets 8 | 0.1.2-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-client-common 13 | Basic rpc wiring and rebinding to be used by any server implementation 14 | 15 | 16 | 17 | ${gwt.groupId} 18 | gwt-user 19 | provided 20 | 21 | 22 | ${gwt.groupId} 23 | gwt-dev 24 | provided 25 | 26 | 27 | 28 | 29 | 30 | 31 | src/main/java 32 | 33 | **/client/**/*.java 34 | **/shared/**/*.java 35 | **/*.gwt.xml 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /workers/src/main/java/prj/gwtwwlinker/workerlinker/WorkerLinker.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/worker/AppWorker.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/worker/AppWorker.gwt.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/worker/MessageEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.worker; 21 | 22 | import jsinterop.annotations.JsFunction; 23 | import jsinterop.annotations.JsPackage; 24 | import jsinterop.annotations.JsProperty; 25 | import jsinterop.annotations.JsType; 26 | 27 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 28 | public class MessageEvent { 29 | 30 | @JsProperty 31 | public native T getData(); 32 | 33 | @JsProperty 34 | public native String getOrigin(); 35 | 36 | //ports 37 | 38 | //source 39 | 40 | @JsFunction 41 | public interface MessageHandler { 42 | void onMessage(MessageEvent event); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/DummyRemoteServiceAsync.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.http.client.RequestBuilder; 23 | import com.google.gwt.user.client.rpc.AsyncCallback; 24 | 25 | /** 26 | * Dummy async interface used to convince RPC internals that a real RPC call is being made. 27 | * 28 | */ 29 | public interface DummyRemoteServiceAsync { 30 | RequestBuilder invoke(ServerInvocation serverInvocation, AsyncCallback callback); 31 | RequestBuilder callback(ServerCallbackInvocation serverCallbackInvocation, AsyncCallback callback); 32 | } 33 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatJoin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import sharedchat.common.client.ChatPage; 23 | 24 | /** 25 | * Created by colin on 2/7/17. 26 | */ 27 | public class ChatJoin implements ChatEvent { 28 | String username; 29 | 30 | public ChatJoin() { 31 | } 32 | 33 | public ChatJoin(String username) { 34 | this.username = username; 35 | } 36 | 37 | public String getUsername() { 38 | return username; 39 | } 40 | 41 | public void setUsername(String username) { 42 | this.username = username; 43 | } 44 | 45 | @Override 46 | public void handle(ChatPage chatPage) { 47 | chatPage.join(getUsername()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatLeave.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import sharedchat.common.client.ChatPage; 23 | 24 | /** 25 | * Created by colin on 2/7/17. 26 | */ 27 | public class ChatLeave implements ChatEvent { 28 | private String username; 29 | 30 | public ChatLeave() { 31 | } 32 | 33 | public ChatLeave(String username) { 34 | this.username = username; 35 | } 36 | 37 | public String getUsername() { 38 | return username; 39 | } 40 | 41 | public void setUsername(String username) { 42 | this.username = username; 43 | } 44 | 45 | @Override 46 | public void handle(ChatPage chatPage) { 47 | chatPage.leave(getUsername()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gwt-websockets-parent 7 | com.colinalworth.gwt.websockets 8 | 0.1.2-SNAPSHOT 9 | 10 | 4.0.0 11 | javaee-websocket-gwt-rpc 12 | 13 | 14 | 15 | com.colinalworth.gwt.websockets 16 | rpc-client-common 17 | ${project.version} 18 | 19 | 20 | 21 | ${gwt.groupId} 22 | gwt-servlet 23 | 24 | 25 | 26 | javax.websocket 27 | javax.websocket-api 28 | 1.0 29 | 30 | 31 | 32 | 33 | 34 | 35 | src/main/java 36 | 37 | **/client/**/*.java 38 | **/shared/**/*.java 39 | **/*.gwt.xml 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/pako/Deflate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.pako; 21 | 22 | import com.google.gwt.core.client.JavaScriptObject; 23 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 24 | import com.google.gwt.typedarrays.shared.ArrayBufferView; 25 | import jsinterop.annotations.JsProperty; 26 | import jsinterop.annotations.JsType; 27 | 28 | @JsType(isNative = true, namespace = "pako") 29 | public class Deflate { 30 | public native boolean push(ArrayBuffer array, boolean last); 31 | public native boolean push(ArrayBufferView array, boolean last); 32 | public native boolean push(String string, boolean last); 33 | 34 | @JsProperty 35 | public native ArrayBufferView getResult(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Server; 23 | import com.google.gwt.core.client.Callback; 24 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 25 | 26 | /** 27 | * Created by colin on 2/7/17. 28 | */ 29 | @RemoteServiceRelativePath("chat") 30 | public interface ChatServer extends Server { 31 | /** 32 | * Brings the user into the chat room, with the given username 33 | * @param username the name to use 34 | */ 35 | void login(String username); 36 | 37 | /** 38 | * Sends the given message to the chatroom 39 | * @param message the message to say to the room 40 | */ 41 | void say(String message); 42 | } 43 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/pako/Inflate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.pako; 21 | 22 | import com.google.gwt.core.client.JavaScriptObject; 23 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 24 | import com.google.gwt.typedarrays.shared.ArrayBufferView; 25 | import jsinterop.annotations.JsProperty; 26 | import jsinterop.annotations.JsType; 27 | 28 | /** 29 | * TODO jsinterop 30 | */ 31 | @JsType(isNative = true, namespace = "pako") 32 | public class Inflate { 33 | public native boolean push(ArrayBuffer array, boolean last); 34 | public native boolean push(ArrayBufferView array, boolean last); 35 | public native boolean push(String string, boolean last); 36 | 37 | @JsProperty 38 | public native ArrayBufferView getResult(); 39 | } 40 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Client; 23 | 24 | /** 25 | * Created by colin on 2/7/17. 26 | */ 27 | public interface ChatClient extends Client { 28 | /** 29 | * Tells the client that a user posted a message to the chat room 30 | * @param username the user who sent the message 31 | * @param message the message the user sent 32 | */ 33 | void say(String username, String message); 34 | 35 | /** 36 | * Indicates that a new user has entered the chat room 37 | * @param username the user who logged in 38 | */ 39 | void join(String username); 40 | 41 | /** 42 | * Indicates that a user has left the chat room 43 | * @param username the user who left 44 | */ 45 | void part(String username); 46 | } 47 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/WorkerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client; 21 | 22 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 23 | 24 | /** 25 | * todo how to differentiate between shared, dedicated, service workers... 26 | */ 27 | public interface WorkerFactory, L extends Endpoint> { 28 | 29 | /** 30 | * Creates a worker with the remote JS connected to the local endpoint. 31 | * @param pathToJs the path to the JS file which will describe this worker 32 | * @param local the local interface which the new remote worker can send messages to 33 | * @return the newly created worker, which will still be starting up. 34 | */ 35 | R createDedicatedWorker(String pathToJs, L local); 36 | 37 | R createSharedWorker(String pathToJs, L local); 38 | 39 | R wrapRemoteMessagePort(MessagePort remote, L local); 40 | } 41 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/common/shared/ChatMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.common.shared; 21 | 22 | import sharedchat.common.client.ChatPage; 23 | 24 | /** 25 | * Created by colin on 2/7/17. 26 | */ 27 | public class ChatMessage implements ChatEvent { 28 | private String username; 29 | private String text; 30 | 31 | public ChatMessage() { 32 | } 33 | 34 | public ChatMessage(String username, String text) { 35 | this.username = username; 36 | this.text = text; 37 | } 38 | 39 | public String getUsername() { 40 | return username; 41 | } 42 | 43 | public void setUsername(String username) { 44 | this.username = username; 45 | } 46 | 47 | public String getText() { 48 | return text; 49 | } 50 | 51 | public void setText(String text) { 52 | this.text = text; 53 | } 54 | 55 | @Override 56 | public void handle(ChatPage chatPage) { 57 | chatPage.say(getUsername(), getText()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/worker/MessagePort.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.worker; 21 | 22 | import com.colinalworth.gwt.worker.client.worker.MessageEvent.MessageHandler; 23 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 24 | import jsinterop.annotations.JsOverlay; 25 | import jsinterop.annotations.JsPackage; 26 | import jsinterop.annotations.JsType; 27 | 28 | /** 29 | * Created by colin on 1/18/16. 30 | */ 31 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 32 | public class MessagePort { 33 | 34 | public final native void postMessage(Object jso, ArrayBuffer[] buffers); 35 | 36 | public native void start(); 37 | 38 | public native void close(); 39 | 40 | @JsOverlay 41 | public final void addMessageHandler(MessageHandler handler) { 42 | addEventListener("message", handler); 43 | } 44 | 45 | public native void addEventListener(String type, MessageHandler listener); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/shared/ChatServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package samples.easychatroom2.shared; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Server; 23 | import com.google.gwt.core.client.Callback; 24 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 25 | 26 | /** 27 | * Simple example of methods a server can have that can be invoked by a client. 28 | * 29 | */ 30 | @RemoteServiceRelativePath("/chat") 31 | public interface ChatServer extends Server { 32 | /** 33 | * Brings the user into the chat room, with the given username 34 | * @param username the name to use 35 | * @param callback indicates the login was successful, or passes back an error message 36 | */ 37 | void login(String username, Callback callback); 38 | 39 | /** 40 | * Sends the given message to the chatroom 41 | * @param message the message to say to the room 42 | */ 43 | void say(String message); 44 | } 45 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gwt-websockets-parent 7 | com.colinalworth.gwt.websockets 8 | 0.1.2-SNAPSHOT 9 | 10 | 4.0.0 11 | war 12 | 13 | javaee-websocket-gwt-rpc-sample 14 | 15 | 16 | 17 | com.colinalworth.gwt.websockets 18 | javaee-websocket-gwt-rpc 19 | ${project.version} 20 | 21 | 22 | 23 | ${gwt.groupId} 24 | gwt-servlet 25 | 26 | 27 | 28 | 29 | ${gwt.groupId} 30 | gwt-user 31 | provided 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ${gwt.plugin.groupId} 40 | gwt-maven-plugin 41 | 42 | 43 | prepare-package 44 | 45 | compile 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.eclipse.jetty 55 | jetty-maven-plugin 56 | 9.2.3.v20140905 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ClientCallbackInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.IsSerializable; 23 | 24 | public class ClientCallbackInvocation implements IsSerializable { 25 | private transient int callbackId; 26 | private transient Object response; 27 | private transient boolean isSuccess; 28 | 29 | public ClientCallbackInvocation() { 30 | } 31 | 32 | public ClientCallbackInvocation(int callbackId, Object response, boolean success) { 33 | this.callbackId = callbackId; 34 | this.response = response; 35 | isSuccess = success; 36 | } 37 | 38 | public int getCallbackId() { 39 | return callbackId; 40 | } 41 | 42 | public void setCallbackId(int callbackId) { 43 | this.callbackId = callbackId; 44 | } 45 | 46 | public Object getResponse() { 47 | return response; 48 | } 49 | 50 | public void setResponse(Object response) { 51 | this.response = response; 52 | } 53 | 54 | public boolean isSuccess() { 55 | return isSuccess; 56 | } 57 | 58 | public void setSuccess(boolean success) { 59 | isSuccess = success; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ServerCallbackInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.IsSerializable; 23 | 24 | public class ServerCallbackInvocation implements IsSerializable { 25 | private transient int callbackId; 26 | private transient Object response; 27 | private transient boolean isSuccess; 28 | 29 | public ServerCallbackInvocation() { 30 | } 31 | 32 | public ServerCallbackInvocation(int callbackId, Object response, boolean success) { 33 | this.callbackId = callbackId; 34 | this.response = response; 35 | isSuccess = success; 36 | } 37 | 38 | public int getCallbackId() { 39 | return callbackId; 40 | } 41 | 42 | public void setCallbackId(int callbackId) { 43 | this.callbackId = callbackId; 44 | } 45 | 46 | public Object getResponse() { 47 | return response; 48 | } 49 | 50 | public void setResponse(Object response) { 51 | this.response = response; 52 | } 53 | 54 | public boolean isSuccess() { 55 | return isSuccess; 56 | } 57 | 58 | public void setSuccess(boolean success) { 59 | isSuccess = success; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /workers-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gwt-websockets-parent 7 | com.colinalworth.gwt.websockets 8 | 0.1.2-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | workers-sample 13 | war 14 | 15 | 16 | 17 | com.colinalworth.gwt.websockets 18 | workers 19 | ${project.version} 20 | provided 21 | 22 | 23 | 24 | com.colinalworth.gwt.websockets 25 | javaee-websocket-gwt-rpc 26 | ${project.version} 27 | 28 | 29 | 30 | 31 | ${gwt.groupId} 32 | gwt-user 33 | provided 34 | 35 | 36 | 37 | 38 | 39 | ${gwt.plugin.groupId} 40 | gwt-maven-plugin 41 | 42 | 43 | prepare-package 44 | 45 | compile 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.eclipse.jetty 55 | jetty-maven-plugin 56 | 9.2.3.v20140905 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/RemoteCallbackInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.google.gwt.user.client.rpc.IsSerializable; 23 | 24 | /** 25 | * Created by colin on 1/18/16. 26 | */ 27 | public class RemoteCallbackInvocation implements IsSerializable { 28 | private transient int callbackId; 29 | private transient Object response; 30 | private transient boolean isSuccess; 31 | 32 | public RemoteCallbackInvocation() { 33 | } 34 | 35 | public RemoteCallbackInvocation(int callbackId, Object response, boolean isSuccess) { 36 | this.callbackId = callbackId; 37 | this.response = response; 38 | this.isSuccess = isSuccess; 39 | } 40 | 41 | public int getCallbackId() { 42 | return callbackId; 43 | } 44 | 45 | public void setCallbackId(int callbackId) { 46 | this.callbackId = callbackId; 47 | } 48 | 49 | public Object getResponse() { 50 | return response; 51 | } 52 | 53 | public void setResponse(Object response) { 54 | this.response = response; 55 | } 56 | 57 | public boolean isSuccess() { 58 | return isSuccess; 59 | } 60 | 61 | public void setSuccess(boolean isSuccess) { 62 | this.isSuccess = isSuccess; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/shared/ChatClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package samples.easychatroom2.shared; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Client; 23 | import com.google.gwt.core.client.Callback; 24 | 25 | /** 26 | * Simple example of methods implemented by a GWT client that can be called from the server 27 | * 28 | */ 29 | public interface ChatClient extends Client { 30 | /** 31 | * Tells the client that a user posted a message to the chat room 32 | * @param username the user who sent the message 33 | * @param message the message the user sent 34 | */ 35 | void say(String username, String message); 36 | 37 | /** 38 | * Indicates that a new user has entered the chat room 39 | * @param username the user who logged in 40 | */ 41 | void join(String username); 42 | 43 | /** 44 | * Indicates that a user has left the chat room 45 | * @param username the user who left 46 | */ 47 | void part(String username); 48 | 49 | 50 | /** 51 | * Test method to have the server send the client a message and get a response right away 52 | * @param callback response that the client should call upon receipt of this method 53 | */ 54 | void ping(Callback callback); 55 | } 56 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/Client.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared; 21 | 22 | /** 23 | * Starting interface for building the methods the server may call on the client. User code may 24 | * provide an instance of this type to a Server instance created using GWT.create, and Client 25 | * instances will be created automatically on the server as connections are created. 26 | * 27 | * @see Server 28 | * 29 | */ 30 | 31 | public interface Client, S extends Server> { 32 | 33 | /** 34 | * Callback called when the connection to the server has been established. Should not be called 35 | * directly from the server. 36 | */ 37 | void onOpen(); 38 | 39 | /** 40 | * Callback called when the connection to the server has been closed. Should not be called 41 | * directly from the server. 42 | */ 43 | void onClose(); 44 | 45 | /** 46 | * Called when an error occurs while handling a message in one of the other client methods. If a 47 | * ConnectionErrorHandler is provided to the server builder, that will be used in handling 48 | * serialization/deserialization and connection errors. 49 | * @param error the error that occurred 50 | */ 51 | void onError(Throwable error); 52 | } 53 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc/src/main/java/com/colinalworth/gwt/websockets/server/AbstractServerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.server; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Client; 23 | import com.colinalworth.gwt.websockets.shared.Server; 24 | 25 | public abstract class AbstractServerImpl, C extends Client> extends RpcEndpoint implements Server { 26 | /** In JSR-356, each server socket instance has exactly one client */ 27 | private C client; 28 | 29 | protected AbstractServerImpl(Class client) { 30 | super(client); 31 | } 32 | 33 | @Override 34 | public void onOpen(Connection connection, C client) { 35 | // default empty implementation to allow clients to define this only if desired 36 | } 37 | 38 | @Override 39 | public void onClose(Connection connection, C client) { 40 | // default empty implementation to allow clients to define this only if desired 41 | } 42 | 43 | @Override 44 | public C getClient() { 45 | return client; 46 | } 47 | 48 | @Override 49 | public void setClient(C client) { 50 | this.client = client; 51 | } 52 | 53 | @Override 54 | public final void close() { 55 | throw new IllegalStateException("This method may not be called on the server, only on the client. To close the connection, invoke WebSocketConnection.close() on the connection you want to stop."); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/worker/client/AppWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package simpleworker.worker.client; 21 | 22 | import com.colinalworth.gwt.worker.client.WorkerFactory; 23 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 24 | import com.google.gwt.core.client.Callback; 25 | import com.google.gwt.core.client.EntryPoint; 26 | import com.google.gwt.core.client.GWT; 27 | import simpleworker.common.client.MyHost; 28 | import simpleworker.common.client.MyWorker; 29 | 30 | /** 31 | * Created by colin on 1/21/16. 32 | */ 33 | public class AppWorker implements EntryPoint { 34 | public interface Factory extends WorkerFactory {} 35 | 36 | @Override 37 | public void onModuleLoad() { 38 | 39 | Factory factory = GWT.create(Factory.class);//new GeneratedWorkerFactory(); 40 | 41 | factory.wrapRemoteMessagePort(self(), new MyWorker() { 42 | @Override 43 | public void ping() { 44 | getRemote().pong(); 45 | } 46 | 47 | @Override 48 | public void split(String pattern, String input, Callback callback) { 49 | try { 50 | callback.onSuccess(input.split(pattern)); 51 | } catch (Exception e) { 52 | callback.onFailure(e); 53 | } 54 | } 55 | 56 | private MyHost remote; 57 | 58 | @Override 59 | public void setRemote(MyHost myHost) { 60 | remote = myHost; 61 | } 62 | 63 | @Override 64 | public MyHost getRemote() { 65 | return remote; 66 | } 67 | }); 68 | } 69 | 70 | private native MessagePort self() /*-{ 71 | return $wnd; 72 | }-*/; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/RemoteCallbackInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class RemoteCallbackInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, RemoteCallbackInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, RemoteCallbackInvocation instance) throws SerializationException { 34 | instance.setCallbackId(streamReader.readInt()); 35 | instance.setSuccess(streamReader.readBoolean()); 36 | instance.setResponse(streamReader.readObject()); 37 | } 38 | 39 | @Override 40 | public void serializeInstance(SerializationStreamWriter streamWriter, RemoteCallbackInvocation instance) throws SerializationException { 41 | serialize(streamWriter, instance); 42 | } 43 | 44 | public static void serialize(SerializationStreamWriter streamWriter, RemoteCallbackInvocation instance) throws SerializationException { 45 | streamWriter.writeInt(instance.getCallbackId()); 46 | streamWriter.writeBoolean(instance.isSuccess()); 47 | streamWriter.writeObject(instance.getResponse()); 48 | } 49 | } -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/AbstractWorkerFactoryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | import com.colinalworth.gwt.worker.client.WorkerFactory; 24 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 25 | import com.colinalworth.gwt.worker.client.worker.SharedWorker; 26 | import com.colinalworth.gwt.worker.client.worker.Worker; 27 | 28 | /** 29 | * base class for generated factories, with a hook to create the remote endpoint to connect to 30 | */ 31 | public abstract class AbstractWorkerFactoryImpl, L extends Endpoint> implements WorkerFactory { 32 | 33 | @Override 34 | public R createDedicatedWorker(String pathToJs, L local) { 35 | 36 | Worker worker = new Worker(pathToJs); 37 | 38 | R remote = create(worker); 39 | 40 | remote.setRemote(local); 41 | local.setRemote(remote); 42 | 43 | return remote; 44 | } 45 | 46 | @Override 47 | public R createSharedWorker(String pathToJs, L local) { 48 | SharedWorker worker = new SharedWorker(pathToJs, pathToJs); 49 | R remote = create(worker.getPort()); 50 | 51 | remote.setRemote(local); 52 | local.setRemote(remote); 53 | 54 | return remote; 55 | } 56 | 57 | @Override 58 | public R wrapRemoteMessagePort(MessagePort remote, L local) { 59 | R r = create(remote); 60 | 61 | r.setRemote(local); 62 | local.setRemote(r); 63 | 64 | return r; 65 | } 66 | 67 | /** 68 | * build the actual instance to connect 69 | */ 70 | protected abstract R create(MessagePort worker); 71 | } 72 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ClientCallbackInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class ClientCallbackInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, ClientCallbackInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, ClientCallbackInvocation instance) throws SerializationException { 34 | instance.setCallbackId(streamReader.readInt()); 35 | instance.setSuccess(streamReader.readBoolean()); 36 | instance.setResponse(streamReader.readObject()); 37 | } 38 | 39 | @Override 40 | public void serializeInstance(SerializationStreamWriter streamWriter, ClientCallbackInvocation instance) throws SerializationException { 41 | serialize(streamWriter, instance); 42 | } 43 | 44 | public static void serialize(SerializationStreamWriter streamWriter, ClientCallbackInvocation instance) throws SerializationException { 45 | streamWriter.writeInt(instance.getCallbackId()); 46 | streamWriter.writeBoolean(instance.isSuccess()); 47 | streamWriter.writeObject(instance.getResponse()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ServerCallbackInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class ServerCallbackInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, ServerCallbackInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, ServerCallbackInvocation instance) throws SerializationException { 34 | instance.setCallbackId(streamReader.readInt()); 35 | instance.setSuccess(streamReader.readBoolean()); 36 | instance.setResponse(streamReader.readObject()); 37 | } 38 | 39 | @Override 40 | public void serializeInstance(SerializationStreamWriter streamWriter, ServerCallbackInvocation instance) throws SerializationException { 41 | serialize(streamWriter, instance); 42 | } 43 | 44 | public static void serialize(SerializationStreamWriter streamWriter, ServerCallbackInvocation instance) throws SerializationException { 45 | streamWriter.writeInt(instance.getCallbackId()); 46 | streamWriter.writeBoolean(instance.isSuccess()); 47 | streamWriter.writeObject(instance.getResponse()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/Server.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared; 21 | 22 | public interface Server, C extends Client> { 23 | 24 | /** 25 | * Called when a client has initiated a connection to the server. Not to be invoked directly 26 | * by the client. 27 | * @param connection 28 | * @param client 29 | */ 30 | void onOpen(Connection connection, C client); 31 | 32 | /** 33 | * Called as a client connection is lost. Not to be invoked directly by the client. 34 | * 35 | * @param connection 36 | * @param client 37 | */ 38 | void onClose(Connection connection, C client); 39 | 40 | void onError(Throwable error); 41 | 42 | /** 43 | * Gets the client for the currently running request, if any. Should return the value last 44 | * passed to {@link #setClient(Client)} (if on the server, within the current thread). 45 | * @return the current client object in use 46 | */ 47 | C getClient(); 48 | 49 | /** 50 | * Sets the current client to call back to. Called on the server to indicate that the given 51 | * client instance is about to make a call. Called by the client to specify which client 52 | * instance should have messages forwarded to it. 53 | * 54 | * @param client the client object to use on this server 55 | */ 56 | void setClient(C client); 57 | 58 | /** 59 | * Closes the connection on the client. Should not be called in server code. 60 | */ 61 | void close(); 62 | 63 | 64 | public static interface Connection { 65 | void data(String key, Object value); 66 | Object data(String key); 67 | 68 | void close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/ConnectionOpenedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.ConnectionOpenedEvent.ConnectionOpenedHandler; 23 | import com.google.gwt.event.shared.EventHandler; 24 | import com.google.gwt.event.shared.GwtEvent; 25 | import com.google.web.bindery.event.shared.HandlerRegistration; 26 | 27 | /** 28 | * Event fired to indicate that a WebSocket connection has been opened, and messages 29 | * may now be sent to the server. 30 | * 31 | */ 32 | public class ConnectionOpenedEvent extends GwtEvent { 33 | private static final GwtEvent.Type TYPE = new GwtEvent.Type(); 34 | 35 | public static GwtEvent.Type getType() { 36 | return TYPE; 37 | } 38 | @Override 39 | public com.google.gwt.event.shared.GwtEvent.Type getAssociatedType() { 40 | return getType(); 41 | } 42 | 43 | @Override 44 | protected void dispatch(ConnectionOpenedHandler handler) { 45 | handler.onConnectionOpened(this); 46 | } 47 | 48 | /** 49 | * EventHandler interface for {@link ConnectionOpenedEvent}. 50 | * 51 | */ 52 | public interface ConnectionOpenedHandler extends EventHandler { 53 | void onConnectionOpened(ConnectionOpenedEvent event); 54 | } 55 | 56 | /** 57 | * Objects implementing this are advertising that they will fire {@link ConnectionOpenedEvent}s 58 | * to {@link ConnectionOpenedHandler}s. 59 | * 60 | */ 61 | public interface HasConnectionOpenedHandlers { 62 | HandlerRegistration addConnectionOpenedHandler(ConnectionOpenedHandler handler); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/RemoteInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.google.gwt.user.client.rpc.GwtTransient; 23 | import com.google.gwt.user.client.rpc.IsSerializable; 24 | 25 | /** 26 | * Created by colin on 1/18/16. 27 | */ 28 | public class RemoteInvocation implements IsSerializable { 29 | private String method; 30 | @GwtTransient 31 | private Object[] parameters; 32 | 33 | private int callbackId; 34 | 35 | public RemoteInvocation() { 36 | } 37 | 38 | public RemoteInvocation(String method, Object[] params, int callbackId) { 39 | this.method = method; 40 | this.parameters = params; 41 | this.callbackId = callbackId; 42 | } 43 | 44 | /** 45 | * Returns the id of the callback on the client that should be invoked when the callback argument is used on the server 46 | * @return a non-zero value if a callback should be invoked 47 | */ 48 | public int getCallbackId() { 49 | return callbackId; 50 | } 51 | 52 | public void setCallbackId(int callbackId) { 53 | this.callbackId = callbackId; 54 | } 55 | 56 | /** 57 | * @return the method 58 | */ 59 | public String getMethod() { 60 | return method; 61 | } 62 | /** 63 | * Package-protected to keep it easy to call by custom serializers 64 | * @param method 65 | */ 66 | void setMethod(String method) { 67 | this.method = method; 68 | } 69 | 70 | /** 71 | * @return the parameters 72 | */ 73 | public Object[] getParameters() { 74 | return parameters; 75 | } 76 | 77 | /** 78 | * Package-protected to keep it easy to call by custom serializers 79 | * @param parameters 80 | */ 81 | void setParameters(Object[] parameters) { 82 | this.parameters = parameters; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/server/ChatServerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.server; 21 | 22 | import com.colinalworth.gwt.websockets.server.AbstractServerImpl; 23 | import sharedchat.common.shared.ChatClient; 24 | import sharedchat.common.shared.ChatServer; 25 | 26 | import javax.websocket.server.ServerEndpoint; 27 | import java.util.Collections; 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.concurrent.ConcurrentHashMap; 31 | 32 | @ServerEndpoint("/chat") 33 | public class ChatServerImpl extends AbstractServerImpl implements ChatServer { 34 | private static final Map loggedIn = new ConcurrentHashMap<>(); 35 | 36 | public ChatServerImpl() { 37 | super(ChatClient.class); 38 | } 39 | 40 | @Override 41 | public void onClose(Connection connection, ChatClient client) { 42 | String userName = loggedIn.remove(client); 43 | if (userName == null) { 44 | return; 45 | } 46 | 47 | for (ChatClient connected : loggedIn.keySet()) { 48 | connected.part(userName); 49 | } 50 | } 51 | 52 | @Override 53 | public void login(String username) { 54 | System.out.println("login: " + username); 55 | 56 | ChatClient c = getClient(); 57 | for (ChatClient connected : loggedIn.keySet()) { 58 | connected.join(username); 59 | } 60 | loggedIn.put(c, username); 61 | } 62 | 63 | @Override 64 | public void say(String message) { 65 | System.out.println("say: " + message); 66 | ChatClient c = getClient(); 67 | String userName = loggedIn.get(c); 68 | 69 | for (ChatClient connected : loggedIn.keySet()) { 70 | connected.say(userName, message); 71 | } 72 | } 73 | 74 | @Override 75 | public void onError(Throwable error) { 76 | error.printStackTrace(); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ClientInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.GwtTransient; 23 | import com.google.gwt.user.client.rpc.IsSerializable; 24 | 25 | /** 26 | * Data passed to the client to tell it a method needs to be invoked. 27 | * 28 | */ 29 | public class ClientInvocation implements IsSerializable { 30 | private int callbackId; 31 | 32 | private String method; 33 | @GwtTransient 34 | private Object[] parameters; 35 | 36 | public ClientInvocation() { 37 | } 38 | 39 | public ClientInvocation(String method, Object[] params, int callbackId) { 40 | this.method = method; 41 | this.parameters = params; 42 | this.callbackId = callbackId; 43 | } 44 | 45 | /** 46 | * Returns the id of the callback on the client that should be invoked when the callback argument is used on the server 47 | * @return a non-zero value if a callback should be invoked 48 | */ 49 | public int getCallbackId() { 50 | return callbackId; 51 | } 52 | 53 | public void setCallbackId(int callbackId) { 54 | this.callbackId = callbackId; 55 | } 56 | 57 | /** 58 | * @return the method 59 | */ 60 | public String getMethod() { 61 | return method; 62 | } 63 | /** 64 | * Package-protected to keep it easy to call by custom serializers 65 | * @param method 66 | */ 67 | void setMethod(String method) { 68 | this.method = method; 69 | } 70 | 71 | /** 72 | * @return the parameters 73 | */ 74 | public Object[] getParameters() { 75 | return parameters; 76 | } 77 | 78 | /** 79 | * Package-protected to keep it easy to call by custom serializers 80 | * @param parameters 81 | */ 82 | void setParameters(Object[] parameters) { 83 | this.parameters = parameters; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/ConnectionClosedEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.ConnectionClosedEvent.ConnectionClosedHandler; 23 | import com.google.gwt.event.shared.EventHandler; 24 | import com.google.gwt.event.shared.GwtEvent; 25 | import com.google.web.bindery.event.shared.HandlerRegistration; 26 | 27 | /** 28 | * Event fired to indicate that a WebSocket connection has closed for whatever reason, 29 | * allowing the client to attempt to re-open. 30 | * 31 | * Note that there is currently no support to reuse a Server impl, a new one must be created. 32 | * 33 | */ 34 | public class ConnectionClosedEvent extends GwtEvent { 35 | private static final GwtEvent.Type TYPE = new GwtEvent.Type(); 36 | public static GwtEvent.Type getType() { 37 | return TYPE; 38 | } 39 | 40 | @Override 41 | public GwtEvent.Type getAssociatedType() { 42 | return getType(); 43 | } 44 | 45 | @Override 46 | protected void dispatch(ConnectionClosedHandler handler) { 47 | handler.onConnectionClosed(this); 48 | } 49 | 50 | /** 51 | * EventHandler interface for {@link ConnectionClosedEvent}. 52 | * 53 | */ 54 | public interface ConnectionClosedHandler extends EventHandler { 55 | void onConnectionClosed(ConnectionClosedEvent event); 56 | } 57 | 58 | /** 59 | * Objects implementing this are advertising that they will fire {@link ConnectionClosedEvent}s 60 | * to {@link ConnectionClosedHandler}s. 61 | * 62 | */ 63 | public interface HasConnectionClosedHandlers { 64 | HandlerRegistration addConnectionClosedHandler(ConnectionClosedHandler handler); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ServerInvocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.GwtTransient; 23 | import com.google.gwt.user.client.rpc.IsSerializable; 24 | 25 | /** 26 | * Data passed to the server to tell it a method needs to be invoked. 27 | * 28 | */ 29 | public class ServerInvocation implements IsSerializable { 30 | private int callbackId; 31 | 32 | private String method; 33 | @GwtTransient 34 | private Object[] parameters; 35 | 36 | public ServerInvocation() { 37 | } 38 | 39 | public ServerInvocation(String method, Object[] parameters, int callbackId) { 40 | this.method = method; 41 | this.parameters = parameters; 42 | this.callbackId = callbackId; 43 | } 44 | 45 | 46 | /** 47 | * Returns the id of the callback on the client that should be invoked when the callback argument is used on the server 48 | * @return a non-zero value if a callback should be invoked 49 | */ 50 | public int getCallbackId() { 51 | return callbackId; 52 | } 53 | 54 | public void setCallbackId(int callbackId) { 55 | this.callbackId = callbackId; 56 | } 57 | 58 | /** 59 | * @return the method 60 | */ 61 | public String getMethod() { 62 | return method; 63 | } 64 | 65 | /** 66 | * Package-protected to keep it easy to call by custom serializers 67 | * @param method 68 | */ 69 | void setMethod(String method) { 70 | this.method = method; 71 | } 72 | 73 | /** 74 | * @return the parameters 75 | */ 76 | public Object[] getParameters() { 77 | return parameters; 78 | } 79 | 80 | /** 81 | * Package-protected to keep it easy to call by custom serializers 82 | * @param parameters 83 | */ 84 | void setParameters(Object[] parameters) { 85 | this.parameters = parameters; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ServerInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class ServerInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, ServerInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, ServerInvocation instance) throws SerializationException { 34 | instance.setMethod(streamReader.readString()); 35 | instance.setCallbackId(streamReader.readInt()); 36 | 37 | int length = streamReader.readInt(); 38 | Object[] params = new Object[length]; 39 | for (int i = 0; i < length; i++) { 40 | params[i] = streamReader.readObject(); 41 | } 42 | instance.setParameters(params); 43 | } 44 | 45 | @Override 46 | public void serializeInstance(SerializationStreamWriter streamWriter, ServerInvocation instance) throws SerializationException { 47 | serialize(streamWriter, instance); 48 | } 49 | 50 | public static void serialize(SerializationStreamWriter streamWriter,ServerInvocation instance) throws SerializationException { 51 | streamWriter.writeString(instance.getMethod()); 52 | streamWriter.writeInt(instance.getCallbackId()); 53 | 54 | streamWriter.writeInt(instance.getParameters().length); 55 | Object[] parameters = instance.getParameters(); 56 | for (int i = 0; i < parameters.length; i++) { 57 | Object param = parameters[i]; 58 | streamWriter.writeObject(param); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/ui/client/ChatClientWidget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.ui.client; 21 | 22 | import com.google.gwt.core.client.Callback; 23 | import com.google.gwt.dom.client.Style.Unit; 24 | import com.google.gwt.user.client.Window; 25 | import com.google.gwt.user.client.ui.Button; 26 | import com.google.gwt.user.client.ui.DockLayoutPanel; 27 | import com.google.gwt.user.client.ui.FlowPanel; 28 | import com.google.gwt.user.client.ui.HorizontalPanel; 29 | import com.google.gwt.user.client.ui.IsWidget; 30 | import com.google.gwt.user.client.ui.Label; 31 | import com.google.gwt.user.client.ui.ScrollPanel; 32 | import com.google.gwt.user.client.ui.TextBox; 33 | import com.google.gwt.user.client.ui.Widget; 34 | 35 | /** 36 | * Created by colin on 2/7/17. 37 | */ 38 | public class ChatClientWidget implements IsWidget { 39 | FlowPanel panel = new FlowPanel(); 40 | TextBox message = new TextBox(); 41 | DockLayoutPanel root; 42 | Button send = new Button("Send"); 43 | 44 | public ChatClientWidget() { 45 | root = new DockLayoutPanel(Unit.PX); 46 | 47 | HorizontalPanel input = new HorizontalPanel(); 48 | message.setWidth("200px"); 49 | input.add(message); 50 | input.add(send); 51 | 52 | root.addSouth(input, 30); 53 | ScrollPanel scroll = new ScrollPanel(); 54 | scroll.add(panel); 55 | root.add(scroll); 56 | } 57 | 58 | @Override 59 | public Widget asWidget() { 60 | return root; 61 | } 62 | 63 | public void say(String username, String message) { 64 | addMessage(username + ": " + message); 65 | } 66 | 67 | public void join(String username) { 68 | addMessage(username + " has joined"); 69 | } 70 | 71 | public void leave(String username) { 72 | addMessage(username + " has left"); 73 | } 74 | 75 | protected void addMessage(String message) { 76 | Label m = new Label(message); 77 | panel.add(m); 78 | m.getElement().scrollIntoView(); 79 | } 80 | 81 | public void clear() { 82 | panel.clear(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /workers/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | gwt-websockets-parent 7 | com.colinalworth.gwt.websockets 8 | 0.1.2-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | workers 13 | Easy worker communication of RPC-able objects 14 | 15 | 16 | 17 | ${gwt.groupId} 18 | gwt-user 19 | provided 20 | 21 | 22 | ${gwt.groupId} 23 | gwt-dev 24 | provided 25 | 26 | 27 | io.playn 28 | playn-html 29 | 2.0-rc4 30 | 31 | 32 | io.playn 33 | playn-html 34 | 2.0-rc4 35 | sources 36 | 37 | 38 | 39 | com.colinalworth.gwt.websockets 40 | rpc-client-common 41 | ${project.version} 42 | 43 | 44 | 45 | org.webjars.bower 46 | pako 47 | 0.2.8 48 | 49 | 50 | junit 51 | junit 52 | 53 | 54 | 55 | 56 | 57 | 58 | src/main/java 59 | 60 | **/client/**/*.java 61 | **/shared/**/*.java 62 | **/*.gwt.xml 63 | 64 | 65 | 66 | 67 | 68 | ${gwt.plugin.groupId} 69 | gwt-maven-plugin 70 | 71 | 72 | test 73 | 74 | test 75 | 76 | 77 | 78 | 79 | true 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/RemoteInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class RemoteInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, RemoteInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, RemoteInvocation instance) throws SerializationException { 34 | instance.setMethod(streamReader.readString()); 35 | instance.setCallbackId(streamReader.readInt()); 36 | int length = streamReader.readInt(); 37 | Object[] params = new Object[length]; 38 | for (int i = 0; i < length; i++) { 39 | params[i] = streamReader.readObject(); 40 | } 41 | instance.setParameters(params); 42 | } 43 | 44 | @Override 45 | public void serializeInstance(SerializationStreamWriter streamWriter, RemoteInvocation instance) throws SerializationException { 46 | serialize(streamWriter, instance); 47 | } 48 | 49 | public static void serialize(SerializationStreamWriter streamWriter, RemoteInvocation instance) throws SerializationException { 50 | streamWriter.writeString(instance.getMethod()); 51 | streamWriter.writeInt(instance.getCallbackId()); 52 | if (instance.getParameters() == null) { 53 | streamWriter.writeInt(0); 54 | } else { 55 | streamWriter.writeInt(instance.getParameters().length); 56 | Object[] parameters = instance.getParameters(); 57 | for (int i = 0; i < parameters.length; i++) { 58 | Object param = parameters[i]; 59 | streamWriter.writeObject(param); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/impl/WebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client.impl; 21 | 22 | import com.google.gwt.core.client.JavaScriptObject; 23 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 24 | import com.google.gwt.typedarrays.shared.ArrayBufferView; 25 | import com.google.gwt.user.client.Event; 26 | import jsinterop.annotations.JsFunction; 27 | import jsinterop.annotations.JsPackage; 28 | import jsinterop.annotations.JsType; 29 | 30 | /** 31 | * Simple JSO wrapper the WebSocket object. 32 | * 33 | */ 34 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 35 | public class WebSocket { 36 | 37 | public static int CLOSED; 38 | public static int CLOSING; 39 | public static int CONNECTING; 40 | public static int OPEN; 41 | 42 | public String binaryType; 43 | public int bufferedAmount; 44 | public OnCloseCallback onclose; 45 | public OnMessageCallback onmessage; 46 | public OnOpenCallback onopen; 47 | public OnErrorCallback onerror; 48 | public int readyState; 49 | public String url; 50 | 51 | public WebSocket(String url) { 52 | } 53 | 54 | public native boolean send(ArrayBufferView data); 55 | 56 | public native boolean send(String data); 57 | 58 | public native boolean send(ArrayBuffer data); 59 | 60 | public native void close(); 61 | 62 | @JsFunction 63 | public interface OnCloseCallback { 64 | void onClose(Event a); 65 | 66 | } 67 | @JsFunction 68 | public interface OnMessageCallback { 69 | void onMessage(MessageEvent a); 70 | 71 | } 72 | @JsFunction 73 | public interface OnOpenCallback { 74 | void onOpen(Event a); 75 | } 76 | 77 | @JsFunction 78 | public interface OnErrorCallback { 79 | void onError(JavaScriptObject error); 80 | } 81 | 82 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 83 | public static class MessageEvent { 84 | public T data; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /workers/src/test/java/com/colinalworth/gwt/worker/client/BrokenGwtTestEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client; 21 | 22 | import com.google.gwt.core.client.Callback; 23 | import com.google.gwt.core.client.GWT; 24 | import com.google.gwt.junit.client.GWTTestCase; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /** 30 | * Created by colin on 1/14/16. 31 | */ 32 | public class BrokenGwtTestEndpoint extends GWTTestCase { 33 | @Override 34 | public String getModuleName() { 35 | return "com.colinalworth.gwt.worker.RpcToWorkers"; 36 | } 37 | 38 | public interface MyHost extends Endpoint { 39 | void ping(); 40 | } 41 | public interface MyWorker extends Endpoint { 42 | void pong(); 43 | 44 | void split(String input, String pattern, Callback, Throwable> callback); 45 | } 46 | public interface MyWorkerFactory extends WorkerFactory { 47 | MyWorkerFactory instance = GWT.create(WorkerFactory.class); 48 | } 49 | 50 | 51 | public void testSimpleEndpoint() { 52 | delayTestFinish(1000); 53 | 54 | MyWorker worker = MyWorkerFactory.instance.createDedicatedWorker("simpleWorker.js", new MyHost() { 55 | @Override 56 | public void ping() { 57 | remote.pong(); 58 | } 59 | 60 | private MyWorker remote; 61 | @Override 62 | public void setRemote(MyWorker myWorker) { 63 | remote = myWorker; 64 | remote.split("a,b,c", ",", new Callback, Throwable>() { 65 | @Override 66 | public void onFailure(Throwable throwable) { 67 | fail(throwable.getMessage()); 68 | } 69 | 70 | @Override 71 | public void onSuccess(List strings) { 72 | List expected = new ArrayList(); 73 | expected.add("a"); 74 | expected.add("b"); 75 | expected.add("c"); 76 | assertEquals(expected, strings); 77 | finishTest(); 78 | } 79 | }); 80 | } 81 | 82 | @Override 83 | public MyWorker getRemote() { 84 | return remote; 85 | } 86 | }); 87 | } 88 | } -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/shared/impl/ClientInvocation_CustomFieldSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.shared.impl; 21 | 22 | import com.google.gwt.user.client.rpc.CustomFieldSerializer; 23 | import com.google.gwt.user.client.rpc.SerializationException; 24 | import com.google.gwt.user.client.rpc.SerializationStreamReader; 25 | import com.google.gwt.user.client.rpc.SerializationStreamWriter; 26 | 27 | public class ClientInvocation_CustomFieldSerializer extends CustomFieldSerializer { 28 | @Override 29 | public void deserializeInstance(SerializationStreamReader streamReader, ClientInvocation instance) throws SerializationException { 30 | deserialize(streamReader, instance); 31 | } 32 | 33 | public static void deserialize(SerializationStreamReader streamReader, ClientInvocation instance) throws SerializationException { 34 | instance.setMethod(streamReader.readString()); 35 | instance.setCallbackId(streamReader.readInt()); 36 | 37 | int length = streamReader.readInt(); 38 | Object[] params = new Object[length]; 39 | for (int i = 0; i < length; i++) { 40 | params[i] = streamReader.readObject(); 41 | } 42 | instance.setParameters(params); 43 | } 44 | 45 | @Override 46 | public void serializeInstance(SerializationStreamWriter streamWriter, ClientInvocation instance) throws SerializationException { 47 | serialize(streamWriter, instance); 48 | } 49 | 50 | public static void serialize(SerializationStreamWriter streamWriter,ClientInvocation instance) throws SerializationException { 51 | streamWriter.writeString(instance.getMethod()); 52 | streamWriter.writeInt(instance.getCallbackId()); 53 | 54 | if (instance.getParameters() == null) { 55 | streamWriter.writeInt(0); 56 | } else { 57 | streamWriter.writeInt(instance.getParameters().length); 58 | Object[] parameters = instance.getParameters(); 59 | for (int i = 0; i < parameters.length; i++) { 60 | Object param = parameters[i]; 61 | streamWriter.writeObject(param); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/AbstractClientImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.ConnectionClosedEvent.ConnectionClosedHandler; 23 | import com.colinalworth.gwt.websockets.client.ConnectionClosedEvent.HasConnectionClosedHandlers; 24 | import com.colinalworth.gwt.websockets.client.ConnectionOpenedEvent.ConnectionOpenedHandler; 25 | import com.colinalworth.gwt.websockets.client.ConnectionOpenedEvent.HasConnectionOpenedHandlers; 26 | import com.colinalworth.gwt.websockets.shared.Client; 27 | import com.colinalworth.gwt.websockets.shared.Server; 28 | import com.google.gwt.core.shared.GWT; 29 | import com.google.gwt.event.shared.HandlerManager; 30 | import com.google.web.bindery.event.shared.HandlerRegistration; 31 | 32 | /** 33 | * Simple base class for client implementations that issues events on open and close. 34 | * 35 | */ 36 | public abstract class AbstractClientImpl, S extends Server> implements Client, HasConnectionOpenedHandlers, HasConnectionClosedHandlers { 37 | private final HandlerManager handlerManager = new HandlerManager(this); 38 | 39 | protected HandlerManager getHandlerManager() { 40 | return handlerManager; 41 | } 42 | 43 | @Override 44 | public void onOpen() { 45 | handlerManager.fireEvent(new ConnectionOpenedEvent()); 46 | } 47 | 48 | @Override 49 | public void onClose() { 50 | handlerManager.fireEvent(new ConnectionClosedEvent()); 51 | } 52 | 53 | @Override 54 | public void onError(Throwable error) { 55 | GWT.log("An error occurred when handling a message sent to the client, override onError to handle this in your Client implementation", error); 56 | } 57 | 58 | @Override 59 | public HandlerRegistration addConnectionOpenedHandler(ConnectionOpenedHandler handler) { 60 | return handlerManager.addHandler(ConnectionOpenedEvent.getType(), handler); 61 | } 62 | 63 | @Override 64 | public HandlerRegistration addConnectionClosedHandler(ConnectionClosedHandler handler) { 65 | return handlerManager.addHandler(ConnectionClosedEvent.getType(), handler); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/simpleworker/ui/client/AppUI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package simpleworker.ui.client; 21 | 22 | import com.colinalworth.gwt.worker.client.WorkerFactory; 23 | import com.google.gwt.core.client.Callback; 24 | import com.google.gwt.core.client.EntryPoint; 25 | import com.google.gwt.core.client.GWT; 26 | import com.google.gwt.event.dom.client.ClickEvent; 27 | import com.google.gwt.event.dom.client.ClickHandler; 28 | import com.google.gwt.user.client.Window; 29 | import com.google.gwt.user.client.ui.Button; 30 | import com.google.gwt.user.client.ui.RootPanel; 31 | import simpleworker.common.client.MyHost; 32 | import simpleworker.common.client.MyWorker; 33 | 34 | public class AppUI implements EntryPoint { 35 | public interface Factory extends WorkerFactory {} 36 | 37 | @Override 38 | public void onModuleLoad() { 39 | Factory factory = GWT.create(Factory.class);//new GeneratedWorkerFactory(); 40 | 41 | final MyWorker worker = factory.createDedicatedWorker(GWT.getModuleBaseForStaticFiles() + "../simpleworker_worker/worker.js", new MyHost() { 42 | private MyWorker remote; 43 | 44 | @Override 45 | public void setRemote(MyWorker myWorker) { 46 | this.remote = myWorker; 47 | } 48 | 49 | @Override 50 | public MyWorker getRemote() { 51 | return remote; 52 | } 53 | 54 | @Override 55 | public void pong() { 56 | Window.alert("pong"); 57 | } 58 | }); 59 | 60 | 61 | RootPanel.get().add(new Button("Ping", new ClickHandler() { 62 | @Override 63 | public void onClick(ClickEvent clickEvent) { 64 | worker.ping(); 65 | } 66 | })); 67 | RootPanel.get().add(new Button("Split", new ClickHandler() { 68 | @Override 69 | public void onClick(ClickEvent clickEvent) { 70 | String result = Window.prompt("Split this text on \",\"", "a,b,c,d"); 71 | if (result != null) { 72 | worker.split(",", result, new Callback() { 73 | @Override 74 | public void onFailure(Throwable throwable) { 75 | Window.alert("failure: " + throwable.getMessage()); 76 | } 77 | @Override 78 | public void onSuccess(String[] strings) { 79 | Window.alert(strings.length + " items"); 80 | } 81 | }); 82 | } 83 | } 84 | })); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/ServerBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Server; 23 | 24 | public interface ServerBuilder> { 25 | /** 26 | * Sets the full url, including protocol, host, port, and path for the next server connection 27 | * to be started. If called with a non-null value, will override the other setters in this 28 | * builder. 29 | * 30 | * @param url 31 | * @return 32 | */ 33 | ServerBuilder setUrl(String url); 34 | 35 | /** 36 | * Sets the path for the next server to be started. Defaults to the RemoteServiceRelativePath 37 | * annotation value for the Server interface, if any. 38 | * 39 | * @param path 40 | * @return 41 | */ 42 | ServerBuilder setPath(String path); 43 | 44 | /** 45 | * Sets the port of the next server instance to be started. Defaults to the port the current 46 | * page loaded from. 47 | * 48 | * @param port 49 | * @return 50 | */ 51 | ServerBuilder setPort(int port); 52 | 53 | /** 54 | * Sets the hostname for the next server to be started. Defaults to the hostname the current 55 | * page loaded from. 56 | * 57 | * @param hostname 58 | * @return 59 | */ 60 | ServerBuilder setHostname(String hostname); 61 | 62 | /** 63 | * Sets the protocol ("ws" or "wss") to connect with. Defaults to wss if the current page 64 | * loaded using https, and ws otherwise. 65 | * 66 | * @param protocol 67 | * @return 68 | */ 69 | ServerBuilder setProtocol(String protocol); 70 | 71 | /** 72 | * Creates a new instance of the specified server type, starts, and returns it. May 73 | * be called more than once to create additional connections, such as after the first 74 | * is closed. 75 | * 76 | * @return 77 | */ 78 | S start(); 79 | 80 | /** 81 | * Specifies a handler to receive errors when a problem occurs around the protocol: the connection, 82 | * serialization, or other unhandled issues. 83 | * @param errorHandler the handler to send connection errors to 84 | */ 85 | void setConnectionErrorHandler(ConnectionErrorHandler errorHandler); 86 | 87 | /** 88 | * Allows de/serialization and connection problems to be handled rather than rethrowing 89 | * or just pushing to GWT.log. 90 | */ 91 | public interface ConnectionErrorHandler { 92 | void onError(Exception ex); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/client/impl/ServerBuilderImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.client.impl; 21 | 22 | import com.colinalworth.gwt.websockets.client.ServerBuilder; 23 | import com.colinalworth.gwt.websockets.shared.Server; 24 | import com.google.gwt.http.client.URL; 25 | import com.google.gwt.http.client.UrlBuilder; 26 | import com.google.gwt.user.client.Window; 27 | 28 | public abstract class ServerBuilderImpl> implements ServerBuilder { 29 | private String url; 30 | private UrlBuilder urlBuilder = Window.Location.createUrlBuilder(); 31 | private ConnectionErrorHandler errorHandler; 32 | 33 | /** 34 | * 35 | */ 36 | public ServerBuilderImpl(String moduleBaseURL, String remoteServiceRelativePath) { 37 | urlBuilder.setProtocol(Window.Location.getProtocol().startsWith("https") ? "wss": "ws").setHash(null); 38 | 39 | //TODO in a worker moduleBaseURL can be null... 40 | if (remoteServiceRelativePath != null && moduleBaseURL != null) { 41 | 42 | //assume full url, pull out the path 43 | String basePath = moduleBaseURL.substring(moduleBaseURL.indexOf("/", moduleBaseURL.indexOf("://") + 3)); 44 | /* 45 | * If the module relative URL is not null we set the remote service URL to 46 | * be the module base URL plus the module relative remote service URL. 47 | * Otherwise an explicit call to 48 | * ServerBuilder.setPath(String) or setUrl(String) is required. 49 | */ 50 | setPath(basePath + remoteServiceRelativePath); 51 | } 52 | 53 | } 54 | 55 | @Override 56 | public void setConnectionErrorHandler(ConnectionErrorHandler errorHandler) { 57 | this.errorHandler = errorHandler; 58 | } 59 | public ConnectionErrorHandler getErrorHandler() { 60 | return errorHandler; 61 | } 62 | 63 | @Override 64 | public ServerBuilder setUrl(String url) { 65 | this.url = url; 66 | return this; 67 | } 68 | 69 | /** 70 | * @return the url 71 | */ 72 | public String getUrl() { 73 | return url == null ? urlBuilder.buildString() : url; 74 | } 75 | 76 | @Override 77 | public ServerBuilder setProtocol(String protocol) { 78 | urlBuilder.setProtocol(protocol); 79 | return this; 80 | } 81 | @Override 82 | public ServerBuilder setHostname(String hostname) { 83 | urlBuilder.setHost(hostname); 84 | return this; 85 | } 86 | @Override 87 | public ServerBuilder setPort(int port) { 88 | urlBuilder.setPort(port); 89 | return this; 90 | } 91 | @Override 92 | public ServerBuilder setPath(String path) { 93 | urlBuilder.setPath(path); 94 | return this; 95 | } 96 | } -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/client/ChatClientWidget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package samples.easychatroom2.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.AbstractClientImpl; 23 | import com.google.gwt.core.client.Callback; 24 | import samples.easychatroom2.shared.ChatClient; 25 | 26 | import com.google.gwt.dom.client.Style.Unit; 27 | import com.google.gwt.user.client.Window; 28 | import com.google.gwt.user.client.ui.Button; 29 | import com.google.gwt.user.client.ui.DockLayoutPanel; 30 | import com.google.gwt.user.client.ui.FlowPanel; 31 | import com.google.gwt.user.client.ui.HorizontalPanel; 32 | import com.google.gwt.user.client.ui.IsWidget; 33 | import com.google.gwt.user.client.ui.Label; 34 | import com.google.gwt.user.client.ui.ScrollPanel; 35 | import com.google.gwt.user.client.ui.TextBox; 36 | import com.google.gwt.user.client.ui.Widget; 37 | import samples.easychatroom2.shared.ChatServer; 38 | 39 | public class ChatClientWidget extends AbstractClientImpl implements ChatClient, IsWidget { 40 | FlowPanel panel = new FlowPanel(); 41 | TextBox message = new TextBox(); 42 | DockLayoutPanel root; 43 | Button send = new Button("Send"); 44 | 45 | public ChatClientWidget() { 46 | root = new DockLayoutPanel(Unit.PX); 47 | 48 | HorizontalPanel input = new HorizontalPanel(); 49 | message.setWidth("200px"); 50 | input.add(message); 51 | input.add(send); 52 | 53 | root.addSouth(input, 30); 54 | ScrollPanel scroll = new ScrollPanel(); 55 | scroll.add(panel); 56 | root.add(scroll); 57 | } 58 | 59 | @Override 60 | public Widget asWidget() { 61 | return root; 62 | } 63 | 64 | @Override 65 | public void say(String username, String message) { 66 | addMessage(username + ": " + message); 67 | } 68 | 69 | @Override 70 | public void join(String username) { 71 | addMessage(username + " has joined"); 72 | } 73 | 74 | @Override 75 | public void part(String username) { 76 | addMessage(username + " has left"); 77 | } 78 | 79 | @Override 80 | public void ping(Callback callback) { 81 | callback.onSuccess(null); 82 | } 83 | 84 | @Override 85 | public void onOpen() { 86 | super.onOpen(); 87 | addMessage("You've joined the chat"); 88 | } 89 | 90 | @Override 91 | public void onClose() { 92 | super.onClose(); 93 | addMessage("You've left the chat"); 94 | } 95 | 96 | @Override 97 | public void onError(Throwable error) { 98 | Window.alert(error.getMessage()); 99 | } 100 | 101 | protected void addMessage(String message) { 102 | Label m = new Label(message); 103 | panel.add(m); 104 | m.getElement().scrollIntoView(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/server/ChatServerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package samples.easychatroom2.server; 21 | 22 | import com.colinalworth.gwt.websockets.server.AbstractServerImpl; 23 | import com.google.gwt.core.client.Callback; 24 | import samples.easychatroom2.shared.ChatClient; 25 | import samples.easychatroom2.shared.ChatServer; 26 | 27 | import javax.websocket.server.ServerEndpoint; 28 | import java.util.Collections; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | 32 | @ServerEndpoint("/chat") 33 | public class ChatServerImpl extends AbstractServerImpl implements ChatServer { 34 | private static final Map loggedIn = Collections.synchronizedMap(new HashMap()); 35 | 36 | public ChatServerImpl() { 37 | super(ChatClient.class); 38 | } 39 | 40 | @Override 41 | public void onClose(Connection connection, ChatClient client) { 42 | String userName = loggedIn.remove(client); 43 | if (userName == null) { 44 | return; 45 | } 46 | 47 | for (ChatClient connected : loggedIn.keySet()) { 48 | connected.part(userName); 49 | } 50 | } 51 | 52 | @Override 53 | public void login(String username, Callback callback) { 54 | System.out.println("login: " + username); 55 | if (username == null || username.length() == 0) { 56 | callback.onFailure("Non-empty username required"); 57 | } 58 | 59 | ChatClient c = getClient(); 60 | if (loggedIn.containsKey(c)) { 61 | callback.onFailure("Already logged in"); 62 | } 63 | for (String name : loggedIn.values()) { 64 | if (name.equals(username)) { 65 | callback.onFailure("Username already in use"); 66 | } 67 | } 68 | for (ChatClient connected : loggedIn.keySet()) { 69 | connected.join(username); 70 | } 71 | loggedIn.put(c, username); 72 | callback.onSuccess(null); 73 | 74 | final long start = System.nanoTime(); 75 | getClient().ping(new Callback() { 76 | @Override 77 | public void onFailure(Void reason) { 78 | System.err.println("failed login ping"); 79 | } 80 | 81 | @Override 82 | public void onSuccess(Void result) { 83 | System.out.println("login ping in " + (System.nanoTime() - start) / 1000000.0 + "milliseconds"); 84 | } 85 | }); 86 | } 87 | 88 | @Override 89 | public void say(String message) { 90 | System.out.println("say: " + message); 91 | ChatClient c = getClient(); 92 | String userName = loggedIn.get(c); 93 | 94 | for (ChatClient connected : loggedIn.keySet()) { 95 | connected.say(userName, message); 96 | } 97 | } 98 | 99 | @Override 100 | public void onError(Throwable error) { 101 | error.printStackTrace(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc-sample/src/main/java/samples/easychatroom2/client/SampleEntryPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package samples.easychatroom2.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.ConnectionClosedEvent; 23 | import com.colinalworth.gwt.websockets.client.ConnectionOpenedEvent; 24 | import com.colinalworth.gwt.websockets.client.ConnectionOpenedEvent.ConnectionOpenedHandler; 25 | import com.colinalworth.gwt.websockets.client.ServerBuilder; 26 | import com.google.gwt.core.client.Callback; 27 | import com.google.gwt.core.client.EntryPoint; 28 | import com.google.gwt.core.client.GWT; 29 | import com.google.gwt.event.dom.client.ClickEvent; 30 | import com.google.gwt.event.dom.client.ClickHandler; 31 | import com.google.gwt.user.client.Window; 32 | import com.google.gwt.user.client.ui.RootLayoutPanel; 33 | import samples.easychatroom2.shared.ChatServer; 34 | 35 | public class SampleEntryPoint implements EntryPoint { 36 | interface ChatServerBuilder extends ServerBuilder {} 37 | 38 | @Override 39 | public void onModuleLoad() { 40 | // We can't GWT.create the server itself, instead, we need a builder 41 | //final ChatServer server = GWT.create(ChatServer.class); 42 | final ChatServerBuilder builder = GWT.create(ChatServerBuilder.class); 43 | 44 | // Set the url directly, or use the setHost, setPort, etc calls and 45 | //use the @RemoteServiceRelativePath given on the interface 46 | // builder.setUrl("ws://" + Window.Location.getHost() + "/chat"); 47 | // builder.setHostname(Window.Location.getHostName()); 48 | builder.setPath("chat"); 49 | 50 | // Because this is just a demo, we're using Window.prompt to get a username 51 | final String username = Window.prompt("Select a username", ""); 52 | 53 | // Start up the server connection, then plug into it so you get the callbacks 54 | final ChatServer server = builder.start(); 55 | 56 | final ChatClientWidget impl = new ChatClientWidget(); 57 | server.setClient(impl); 58 | 59 | // This listens for the connection to start, so we can log in with the username 60 | // we already picked. 61 | // Remember that you don't need to build this here, it could be in your client 62 | // impl's onOpen method (and the next block in onClose). 63 | impl.addConnectionOpenedHandler(new ConnectionOpenedHandler() { 64 | @Override 65 | public void onConnectionOpened(ConnectionOpenedEvent event) { 66 | server.login(username, new Callback() { 67 | @Override 68 | public void onFailure(String reason) { 69 | Window.alert(reason); 70 | final String username = Window.prompt("Select a username", ""); 71 | server.login(username, this); 72 | } 73 | 74 | @Override 75 | public void onSuccess(Void result) { 76 | RootLayoutPanel.get().add(impl); 77 | } 78 | }); 79 | } 80 | }); 81 | 82 | // Then listen for close too, so we can do something about the lost connection. 83 | impl.addConnectionClosedHandler(new ConnectionClosedEvent.ConnectionClosedHandler() { 84 | @Override 85 | public void onConnectionClosed(ConnectionClosedEvent event) { 86 | // Take the easy/stupid way out and restart the page! 87 | // This will stop at the prompt and wait for a username before connecting 88 | Window.Location.reload(); 89 | } 90 | }); 91 | 92 | impl.send.addClickHandler(new ClickHandler() { 93 | @Override 94 | public void onClick(ClickEvent event) { 95 | server.say(impl.message.getValue()); 96 | impl.message.setValue(""); 97 | } 98 | }); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/rebind/ServerBuilderGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.rebind; 21 | 22 | import com.colinalworth.gwt.websockets.client.ServerBuilder; 23 | import com.colinalworth.gwt.websockets.client.impl.ServerBuilderImpl; 24 | import com.google.gwt.core.client.GWT; 25 | import com.google.gwt.core.ext.Generator; 26 | import com.google.gwt.core.ext.GeneratorContext; 27 | import com.google.gwt.core.ext.TreeLogger; 28 | import com.google.gwt.core.ext.TreeLogger.Type; 29 | import com.google.gwt.core.ext.UnableToCompleteException; 30 | import com.google.gwt.core.ext.typeinfo.JClassType; 31 | import com.google.gwt.core.ext.typeinfo.TypeOracle; 32 | import com.google.gwt.dev.util.Name; 33 | import com.google.gwt.editor.rebind.model.ModelUtils; 34 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 35 | import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; 36 | import com.google.gwt.user.rebind.SourceWriter; 37 | 38 | import java.io.PrintWriter; 39 | 40 | /** 41 | * Generator for ServerBuilder instances. 42 | * 43 | */ 44 | public class ServerBuilderGenerator extends Generator { 45 | 46 | @Override 47 | public String generate(TreeLogger logger, GeneratorContext context, 48 | String typeName) throws UnableToCompleteException { 49 | TypeOracle oracle = context.getTypeOracle(); 50 | JClassType toGenerate = oracle.findType(typeName).isInterface(); 51 | 52 | if (toGenerate == null) { 53 | logger.log(Type.ERROR, "Error generating " + typeName + ", either not an interface, or cannot be reached from client code."); 54 | throw new UnableToCompleteException(); 55 | } 56 | JClassType serverBuilderType = oracle.findType(ServerBuilder.class.getName()); 57 | JClassType serverImplType = ModelUtils.findParameterizationOf(serverBuilderType, toGenerate)[0]; 58 | 59 | // Build an impl so we can call it ourselves 60 | ServerCreator creator = new ServerCreator(serverImplType); 61 | creator.create(logger, context); 62 | 63 | String packageName = toGenerate.getPackage().getName(); 64 | String simpleName = toGenerate.getName().replace('.', '_') + "_Impl"; 65 | 66 | PrintWriter pw = context.tryCreate(logger, packageName, simpleName); 67 | if (pw == null) { 68 | return packageName + "." + simpleName; 69 | } 70 | 71 | ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(packageName, simpleName); 72 | factory.setSuperclass(Name.getSourceNameForClass(ServerBuilderImpl.class) + "<" + serverImplType.getQualifiedSourceName() + ">"); 73 | factory.addImplementedInterface(typeName); 74 | 75 | SourceWriter sw = factory.createSourceWriter(context, pw); 76 | 77 | 78 | RemoteServiceRelativePath path = serverImplType.getAnnotation(RemoteServiceRelativePath.class); 79 | sw.println("public %1$s() {", simpleName); 80 | if (path != null) { 81 | sw.indentln("super(%1$s.getModuleBaseURL(), \"%2$s\");", GWT.class.getName(), path.value()); 82 | } else { 83 | sw.indentln("super(%1$s.getModuleBaseURL(), null);", GWT.class.getName()); 84 | } 85 | sw.println("}"); 86 | 87 | 88 | sw.println(); 89 | // start method 90 | sw.println("public %1$s start() {", serverImplType.getQualifiedSourceName()); 91 | sw.indent(); 92 | sw.println("String url = getUrl();"); 93 | sw.println("if (url == null) {"); 94 | sw.indentln("return new %1$s(getErrorHandler());", creator.getQualifiedSourceName()); 95 | sw.println("} else {"); 96 | sw.indentln("return new %1$s(getErrorHandler(), url);", creator.getQualifiedSourceName()); 97 | sw.println("}"); 98 | 99 | sw.outdent(); 100 | sw.println("}"); 101 | 102 | sw.commit(logger); 103 | 104 | return factory.getCreatedClassName(); 105 | 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/rebind/EndpointFactoryGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.rebind; 21 | 22 | import com.colinalworth.gwt.worker.client.WorkerFactory; 23 | import com.colinalworth.gwt.worker.client.impl.AbstractWorkerFactoryImpl; 24 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 25 | import com.google.gwt.core.ext.Generator; 26 | import com.google.gwt.core.ext.GeneratorContext; 27 | import com.google.gwt.core.ext.TreeLogger; 28 | import com.google.gwt.core.ext.TreeLogger.Type; 29 | import com.google.gwt.core.ext.UnableToCompleteException; 30 | import com.google.gwt.core.ext.typeinfo.JClassType; 31 | import com.google.gwt.core.ext.typeinfo.TypeOracle; 32 | import com.google.gwt.dev.util.Name; 33 | import com.google.gwt.editor.rebind.model.ModelUtils; 34 | import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; 35 | import com.google.gwt.user.rebind.SourceWriter; 36 | 37 | import java.io.PrintWriter; 38 | 39 | /** 40 | * Generator for ServerBuilder instances. 41 | * 42 | */ 43 | public class EndpointFactoryGenerator extends Generator { 44 | 45 | @Override 46 | public String generate(TreeLogger logger, GeneratorContext context, 47 | String typeName) throws UnableToCompleteException { 48 | TypeOracle oracle = context.getTypeOracle(); 49 | JClassType toGenerate = oracle.findType(typeName).isInterface(); 50 | 51 | if (toGenerate == null) { 52 | logger.log(Type.ERROR, "Error generating " + typeName + ", either not an interface, or cannot be reached from client code."); 53 | throw new UnableToCompleteException(); 54 | } 55 | JClassType workerFactoryType = oracle.findType(WorkerFactory.class.getName()); 56 | JClassType remoteEndpointType = ModelUtils.findParameterizationOf(workerFactoryType, toGenerate)[0]; 57 | JClassType localEndpointType = ModelUtils.findParameterizationOf(workerFactoryType, toGenerate)[1]; 58 | 59 | // Build an impl so we can call it ourselves 60 | RemoteEndpointCreator creator = new RemoteEndpointCreator(remoteEndpointType); 61 | creator.create(logger, context); 62 | 63 | String packageName = toGenerate.getPackage().getName(); 64 | String simpleName = toGenerate.getName().replace('.', '_') + "_Impl"; 65 | 66 | PrintWriter pw = context.tryCreate(logger, packageName, simpleName); 67 | if (pw == null) { 68 | return packageName + "." + simpleName; 69 | } 70 | 71 | ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(packageName, simpleName); 72 | factory.setSuperclass(Name.getSourceNameForClass(AbstractWorkerFactoryImpl.class) + "<" + remoteEndpointType.getQualifiedSourceName() + "," + localEndpointType.getQualifiedSourceName() + ">"); 73 | factory.addImplementedInterface(typeName); 74 | 75 | SourceWriter sw = factory.createSourceWriter(context, pw); 76 | 77 | 78 | // RemoteServiceRelativePath path = remoteEndpointType.getAnnotation(RemoteServiceRelativePath.class); 79 | // if (path != null) { 80 | // sw.println("public %1$s() {", simpleName); 81 | // sw.indentln("setPath(\"%1$s\");", path.value()); 82 | // sw.println("}"); 83 | // } 84 | 85 | sw.println(); 86 | // start method 87 | // sw.println("public %1$s start() {", remoteEndpointType.getQualifiedSourceName()); 88 | // sw.indent(); 89 | // sw.println("String url = getUrl();"); 90 | // sw.println("if (url == null) {"); 91 | // sw.indentln("return new %1$s(getErrorHandler());", creator.getQualifiedSourceName()); 92 | // sw.println("} else {"); 93 | // sw.indentln("return new %1$s(getErrorHandler(), url);", creator.getQualifiedSourceName()); 94 | // sw.println("}"); 95 | // 96 | // sw.outdent(); 97 | // sw.println("}"); 98 | 99 | // create method 100 | sw.println("protected %1$s create(%2$s worker) {", remoteEndpointType.getQualifiedSourceName(), MessagePort.class.getName()); 101 | sw.indentln("return new %1$s(worker);", creator.getQualifiedSourceName()); 102 | sw.println("}"); 103 | 104 | sw.commit(logger); 105 | 106 | return factory.getCreatedClassName(); 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/ui/client/AppUI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.ui.client; 21 | 22 | import com.colinalworth.gwt.worker.client.WorkerFactory; 23 | import com.google.gwt.core.client.EntryPoint; 24 | import com.google.gwt.core.client.GWT; 25 | import com.google.gwt.dom.client.Style.Position; 26 | import com.google.gwt.dom.client.Style.Unit; 27 | import com.google.gwt.event.dom.client.ClickHandler; 28 | import com.google.gwt.user.client.ui.Button; 29 | import com.google.gwt.user.client.ui.FlowPanel; 30 | import com.google.gwt.user.client.ui.Label; 31 | import com.google.gwt.user.client.ui.PopupPanel; 32 | import com.google.gwt.user.client.ui.RootLayoutPanel; 33 | import com.google.gwt.user.client.ui.RootPanel; 34 | import com.google.gwt.user.client.ui.TextBox; 35 | import sharedchat.common.client.ChatPage; 36 | import sharedchat.common.client.ChatWorker; 37 | import sharedchat.common.shared.ChatEvent; 38 | import sharedchat.common.shared.ChatJoin; 39 | import sharedchat.common.shared.ChatLeave; 40 | import sharedchat.common.shared.ChatMessage; 41 | 42 | import java.util.List; 43 | 44 | /** 45 | * Created by colin on 2/6/17. 46 | */ 47 | public class AppUI implements EntryPoint { 48 | 49 | private PopupPanel popup; 50 | 51 | public interface Factory extends WorkerFactory {} 52 | @Override 53 | public void onModuleLoad() { 54 | Factory sharedWorkerFactory = GWT.create(Factory.class); 55 | 56 | ChatClientWidget widget = new ChatClientWidget(); 57 | 58 | ChatWorker sharedWorker = sharedWorkerFactory.createSharedWorker("sharedchat_worker/worker.js", new ChatPage() { 59 | private ChatWorker chatWorker; 60 | 61 | @Override 62 | public void init(String username, List events) { 63 | widget.clear(); 64 | if (username == null) { 65 | //hide chat 66 | RootLayoutPanel.get().clear(); 67 | //show login screen 68 | popup.show(); 69 | } else { 70 | popup.hide(); 71 | RootLayoutPanel.get().add(widget); 72 | 73 | infoPopup("Logged in as " + username); 74 | for (ChatEvent event : events) { 75 | if (event instanceof ChatJoin) { 76 | widget.join(((ChatJoin) event).getUsername()); 77 | } else if (event instanceof ChatLeave) { 78 | widget.leave(((ChatLeave) event).getUsername()); 79 | } else if (event instanceof ChatMessage) { 80 | widget.say(((ChatMessage) event).getUsername(), ((ChatMessage) event).getText()); 81 | } 82 | } 83 | } 84 | } 85 | 86 | @Override 87 | public void join(String username) { 88 | widget.join(username); 89 | } 90 | 91 | @Override 92 | public void leave(String username) { 93 | widget.leave(username); 94 | } 95 | 96 | @Override 97 | public void say(String username, String text) { 98 | widget.say(username, text); 99 | } 100 | 101 | @Override 102 | public void setRemote(ChatWorker chatWorker) { 103 | this.chatWorker = chatWorker; 104 | } 105 | 106 | @Override 107 | public ChatWorker getRemote() { 108 | return chatWorker; 109 | } 110 | 111 | @Override 112 | public void connected() { 113 | unmask(); 114 | infoPopup("Reconnected"); 115 | } 116 | 117 | @Override 118 | public void disconnected() { 119 | mask("Connection lost, reconnecting..."); 120 | } 121 | }); 122 | 123 | widget.send.addClickHandler(e -> { 124 | String message = widget.message.getValue(); 125 | if (message != null && message.trim().length() > 0) { 126 | sharedWorker.send(message); 127 | } 128 | }); 129 | 130 | Button logout = new Button("Logout", (ClickHandler) e -> { 131 | sharedWorker.logout(); 132 | }); 133 | logout.getElement().getStyle().setPosition(Position.ABSOLUTE); 134 | logout.getElement().getStyle().setRight(0, Unit.PX); 135 | RootPanel.get().add(logout); 136 | 137 | popup = new PopupPanel(); 138 | popup.setGlassEnabled(true); 139 | FlowPanel w = new FlowPanel(); 140 | w.add(new Label("Pick a username:")); 141 | TextBox username = new TextBox(); 142 | w.add(username); 143 | w.add(new Button("Login", (ClickHandler) e -> sharedWorker.login(username.getValue()))); 144 | popup.setWidget(w); 145 | 146 | mask("Connecting..."); 147 | } 148 | 149 | public void mask(String message) { 150 | 151 | } 152 | public void unmask() { 153 | 154 | } 155 | public void infoPopup(String message) { 156 | 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /workers/src/main/java/prj/gwtwwlinker/workerlinker/code/WorkerLinker.java: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * %%Ignore-License 3 | * This is a part of gwtwwlinker project 4 | * https://github.com/tomekziel/gwtwwlinker 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 7 | * use this file except in compliance with the License. You may obtain a copy of 8 | * the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | * License for the specific language governing permissions and limitations under 16 | * the License. 17 | **************************************************************************/ 18 | package prj.gwtwwlinker.workerlinker.code; 19 | 20 | import java.util.Set; 21 | import java.util.SortedSet; 22 | 23 | import com.google.gwt.core.ext.LinkerContext; 24 | import com.google.gwt.core.ext.TreeLogger; 25 | import com.google.gwt.core.ext.UnableToCompleteException; 26 | import com.google.gwt.core.ext.linker.AbstractLinker; 27 | import com.google.gwt.core.ext.linker.ArtifactSet; 28 | import com.google.gwt.core.ext.linker.CompilationResult; 29 | import com.google.gwt.core.ext.linker.ConfigurationProperty; 30 | import com.google.gwt.core.ext.linker.LinkerOrder; 31 | import com.google.gwt.dev.About; 32 | import com.google.gwt.dev.util.DefaultTextOutput; 33 | 34 | /** 35 | * This linker removes unnecessary GWT stuff to make the generated JS work inside HTML5 web worker 36 | * Part of https://github.com/tomekziel/gwtwwlinker 37 | * 38 | * Derived from project Titaniumj Mobile, where it was described as 39 | * "This linker removes unnecessary GWT stuff to 40 | * make the generated JS work inside Titanium 41 | * TiMobileHybridLinker.java is part of Ti4j 3.1.0 Copyright 2013 Emitrom LLC" 42 | * https://github.com/emitrom/titanium4j/blob/master/src/com/emitrom/ti4j/mobile/linker/TiMobileLinker.java 43 | */ 44 | @LinkerOrder(LinkerOrder.Order.PRIMARY) 45 | public class WorkerLinker extends AbstractLinker { 46 | 47 | public static final String OUTPUT_NAME_PROPERTY = "worker.filename"; 48 | 49 | @Override 50 | public String getDescription() { 51 | return "gwtwwlinker - generate js for html5 web worker"; 52 | } 53 | 54 | public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) 55 | throws UnableToCompleteException { 56 | 57 | ArtifactSet toReturn = new ArtifactSet(artifacts); 58 | DefaultTextOutput out = new DefaultTextOutput(true); 59 | long compilationTime = System.currentTimeMillis(); 60 | out.print("(function(){"); 61 | out.newline(); 62 | 63 | // get compilation result 64 | Set results = artifacts.find(CompilationResult.class); 65 | if (results.size() == 0) { 66 | logger.log(TreeLogger.WARN, "Requested 0 permutations"); 67 | return toReturn; 68 | } 69 | 70 | CompilationResult result = results.iterator().next(); 71 | 72 | // get the generated javascript 73 | String[] javaScript = result.getJavaScript(); 74 | out.print("var $wnd = self, $doc, $entry, $workergwtbridge, $moduleName, $moduleBase;"); 75 | out.newline(); 76 | // out.print("if(typeof(window) != 'undefined'){ $wnd = window; $doc = $wnd.document; }"); 77 | // out.newline(); 78 | // out.print("else{ $wnd = {JSON: JSON}; }"); // gwtwwlinker - mind the $wnd.JSON passthrough used by autobeans 79 | // out.newline(); 80 | out.print("var $gwt_version = \"" + About.getGwtVersionNum() + "\";"); 81 | out.newlineOpt(); 82 | out.print(javaScript[0]); 83 | out.newline(); 84 | out.print("var $stats = function(){};"); 85 | out.newline(); 86 | out.print("var $sessionId = function(){};"); 87 | out.newline(); 88 | // out.print("var navigator = {};"); 89 | // out.newline(); 90 | // out.print("navigator.userAgent = 'timobile';"); 91 | // out.newline(); 92 | out.print("$strongName = '" + result.getStrongName() + "';"); 93 | out.newline(); 94 | // out.print("$ti4jCompilationDate = " + compilationTime + ";"); 95 | // out.newline(); 96 | // out.print("$wnd.Array = function(){};"); 97 | // out.newline(); 98 | 99 | // gwtwwlinker - register web worker message receiver and pass it to the bridge function 100 | // registered in SampleWorker class 101 | // out.print("self.addEventListener('message', $entry(function(e) { $workergwtbridge(e.data); }), false);"); 102 | // out.newline(); 103 | 104 | out.print("gwtOnLoad(null,'" + context.getModuleName() + "',null);"); 105 | out.newline(); 106 | out.print("})();"); 107 | out.newline(); 108 | 109 | 110 | // gwtwwlinker - WARNING! You must take care of naming your result JS here 111 | // this demo doesn't care of multiple permutation and cache issues! 112 | 113 | String filename = getFilenamePropertyValue(context.getConfigurationProperties()); 114 | 115 | toReturn.add(emitString(logger, out.toString(), filename)); 116 | 117 | return toReturn; 118 | } 119 | 120 | private String getFilenamePropertyValue(SortedSet props) { 121 | for (ConfigurationProperty prop : props) { 122 | if (prop.getName().equals(OUTPUT_NAME_PROPERTY)) { 123 | return prop.getValues().get(0); 124 | } 125 | } 126 | 127 | throw new IllegalStateException("Can't find property " + OUTPUT_NAME_PROPERTY); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/StreamReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.colinalworth.gwt.worker.client.pako.Inflate; 23 | import com.google.gwt.core.client.JsArrayString; 24 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 25 | import com.google.gwt.user.client.rpc.SerializationException; 26 | import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader; 27 | import com.google.gwt.user.client.rpc.impl.Serializer; 28 | import playn.html.TypedArrayHelper; 29 | 30 | import java.nio.ByteBuffer; 31 | import java.nio.ByteOrder; 32 | import java.nio.IntBuffer; 33 | 34 | public class StreamReader extends AbstractSerializationStreamReader { 35 | private final Serializer serializer;//b 36 | private final ByteBuffer bb; 37 | private final IntBuffer payload; 38 | private final JsArrayString strings; 39 | 40 | public StreamReader(Serializer serializer, ByteBuffer bb, JsArrayString strings) { 41 | this.serializer = serializer; 42 | this.bb = bb; 43 | this.payload = bb.asIntBuffer(); 44 | this.strings = strings; 45 | int version = payload.get(); 46 | int flags = payload.get(); 47 | int length = payload.get(); 48 | assert length == payload.remaining(); 49 | setVersion(version); 50 | setFlags(flags); 51 | } 52 | 53 | public StreamReader(Serializer serializer, ArrayBuffer data) { 54 | this.serializer = serializer; 55 | 56 | //TODO if StreamWriter might not compress, check that we've got something that can be compressed... 57 | //unzip data before continuing 58 | Inflate unzip = new Inflate(); 59 | unzip.push(data, true); 60 | data = unzip.getResult().buffer(); 61 | 62 | bb = TypedArrayHelper.wrap(data); 63 | bb.order(ByteOrder.nativeOrder()); 64 | IntBuffer ints = bb.asIntBuffer(); 65 | int version = ints.get(); 66 | int flags = ints.get(); 67 | int length = ints.get(); 68 | setVersion(version); 69 | setFlags(flags); 70 | payload = ints.slice(); 71 | payload.limit(length); 72 | 73 | strings = JsArrayString.createArray().cast(); 74 | if (ints.limit() > 3 + length) {//if there is at least one entry after payload 75 | int stringsCount = ints.get(3 + length); 76 | assert stringsCount >= 0;//shouldn't have written count for zero strings 77 | bb.position((4 + length) << 2); 78 | 79 | for (int i = 0; i < stringsCount; i++) { 80 | int stringLength = bb.getInt(); 81 | byte[] bytes = new byte[stringLength]; 82 | bb.get(bytes); 83 | strings.push(new String(bytes)); 84 | } 85 | } 86 | bb.position(3 << 2); 87 | } 88 | 89 | @Override 90 | public void prepareToRead(String encoded) throws SerializationException { 91 | assert false : "Do not call this, serialized strings are not supported"; 92 | } 93 | 94 | @Override 95 | protected Object deserialize(String s) throws SerializationException { 96 | int id = reserveDecodedObjectIndex(); 97 | Object instance = serializer.instantiate(this, s); 98 | rememberDecodedObject(id, instance); 99 | serializer.deserialize(this, instance, s); 100 | return instance; 101 | } 102 | 103 | @Override 104 | protected String getString(int i) { 105 | return i > 0 ? strings.get(i - 1) : null; 106 | } 107 | 108 | @Override 109 | public boolean readBoolean() throws SerializationException { 110 | return payload.get() == 1;//or zero 111 | } 112 | 113 | @Override 114 | public byte readByte() throws SerializationException { 115 | return (byte) payload.get(); 116 | } 117 | 118 | @Override 119 | public char readChar() throws SerializationException { 120 | return (char) payload.get(); 121 | } 122 | 123 | @Override 124 | public double readDouble() throws SerializationException { 125 | return Double.longBitsToDouble(readLong()); 126 | } 127 | 128 | @Override 129 | public float readFloat() throws SerializationException { 130 | 131 | int position = payload.position(); 132 | payload.position(position + 1); 133 | return bb.asFloatBuffer().get(position); 134 | } 135 | 136 | @Override 137 | public int readInt() throws SerializationException { 138 | return payload.get(); 139 | } 140 | 141 | @Override 142 | public long readLong() throws SerializationException { 143 | int[] a = new int[3]; 144 | a[0] = readInt(); 145 | a[1] = readInt(); 146 | a[2] = readInt(); 147 | assert a[0] == (a[0] & StreamWriter.MASK); 148 | assert a[1] == (a[1] & StreamWriter.MASK); 149 | assert a[2] == (a[2] & StreamWriter.MASK_2); 150 | return (long) a[0] + ((long) a[1] << (long) StreamWriter.BITS) + ((long) a[2] << (long) StreamWriter.BITS01); 151 | } 152 | 153 | @Override 154 | public short readShort() throws SerializationException { 155 | return (short) payload.get(); 156 | } 157 | 158 | @Override 159 | public String readString() throws SerializationException { 160 | return getString(readInt()); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/AbstractWorkerEndpointImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.colinalworth.gwt.worker.client.Endpoint; 23 | import com.colinalworth.gwt.worker.client.worker.MessageEvent; 24 | import com.colinalworth.gwt.worker.client.worker.MessageEvent.MessageHandler; 25 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 26 | import com.google.gwt.core.client.Callback; 27 | import com.google.gwt.core.client.JsArrayMixed; 28 | import com.google.gwt.core.client.JsArrayString; 29 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 30 | import com.google.gwt.user.client.rpc.SerializationException; 31 | import com.google.gwt.user.client.rpc.impl.Serializer; 32 | import playn.html.TypedArrayHelper; 33 | 34 | import java.nio.ByteBuffer; 35 | import java.nio.ByteOrder; 36 | import java.util.HashMap; 37 | import java.util.Map; 38 | 39 | /** 40 | * Base implementation of Endpoint 41 | */ 42 | public abstract class AbstractWorkerEndpointImpl, REMOTE extends Endpoint> implements Endpoint { 43 | private final MessagePort worker; 44 | private REMOTE remote; 45 | 46 | private int nextCallbackId = 1; 47 | private Map> callbacks = new HashMap>(); 48 | 49 | 50 | protected AbstractWorkerEndpointImpl(MessagePort worker) { 51 | this.worker = worker; 52 | worker.addMessageHandler(new MessageHandler() { 53 | @Override 54 | public void onMessage(MessageEvent event) { 55 | try { 56 | __onMessage(event); 57 | } catch (SerializationException e) { 58 | //TODO handle exception 59 | } 60 | } 61 | }); 62 | worker.start(); 63 | } 64 | 65 | @Override 66 | public void setRemote(REMOTE remote) { 67 | this.remote = remote; 68 | } 69 | 70 | @Override 71 | public REMOTE getRemote() { 72 | return remote; 73 | } 74 | 75 | protected abstract Serializer __getSerializer(); 76 | 77 | 78 | public void __onMessage(MessageEvent message) throws SerializationException { 79 | __checkLocal(); 80 | //message is two parts, payload and strings 81 | JsArrayMixed data = message.getData(); 82 | ByteBuffer byteBuffer = TypedArrayHelper.wrap(data.getObject(0)); 83 | byteBuffer.order(ByteOrder.nativeOrder()); 84 | JsArrayString strings = data.getObject(1); 85 | StreamReader reader = new StreamReader(__getSerializer(), byteBuffer, strings); 86 | 87 | Object object = reader.readObject(); 88 | 89 | try { 90 | if (object instanceof RemoteInvocation) { 91 | final RemoteInvocation invocation = (RemoteInvocation) object; 92 | //TODO handle passed callback 93 | if (invocation.getCallbackId() != 0) { 94 | Callback callback = new Callback() { 95 | private boolean fired = false; 96 | @Override 97 | public void onFailure(Object reason) { 98 | checkFired(); 99 | __sendCallback(invocation.getCallbackId(), reason, false); 100 | } 101 | 102 | @Override 103 | public void onSuccess(Object result) { 104 | checkFired(); 105 | __sendCallback(invocation.getCallbackId(), result, true); 106 | } 107 | 108 | private void checkFired() { 109 | if (fired) { 110 | throw new IllegalStateException("Callback already used, cannot be used again."); 111 | } 112 | fired = true; 113 | } 114 | }; 115 | //This is only legal in gwt'd java, where object arrays are backed by a js array 116 | invocation.getParameters()[invocation.getParameters().length] = callback; 117 | } 118 | 119 | __invoke(invocation.getMethod(), invocation.getParameters()); 120 | 121 | 122 | //TODO handle responding callback 123 | } else { 124 | assert object instanceof RemoteCallbackInvocation; 125 | RemoteCallbackInvocation callback = (RemoteCallbackInvocation) object; 126 | __callback(callback.getCallbackId(), callback.getResponse(), callback.isSuccess()); 127 | 128 | } 129 | } catch (Exception ex) { 130 | //TODO handle exception, do not rethrow 131 | } 132 | } 133 | 134 | private void __sendCallback(int callbackId, Object response, boolean isSuccess) { 135 | RemoteCallbackInvocation callbackInvoke = new RemoteCallbackInvocation(callbackId, response, isSuccess); 136 | 137 | StreamWriter writer = new StreamWriter(__getSerializer()); 138 | 139 | try { 140 | writer.writeObject(callbackInvoke); 141 | 142 | JsArrayMixed workerData = writer.getWorkerData(); 143 | worker.postMessage(workerData, new ArrayBuffer[]{workerData.getObject(0)}); 144 | } catch (SerializationException e) { 145 | //TODO report, rethrow 146 | } 147 | } 148 | 149 | private void __callback(int callbackId, Object response, boolean success) { 150 | if (callbacks.containsKey(callbackId)) { 151 | @SuppressWarnings("unchecked") Callback c = (Callback) callbacks.remove(callbackId); 152 | if (success) { 153 | c.onSuccess(response); 154 | } else { 155 | c.onFailure(response); 156 | } 157 | } 158 | } 159 | 160 | protected void __sendMessage(String methodName, Callback callback, Object... params) { 161 | final int callbackId; 162 | if (callback != null) { 163 | callbackId = nextCallbackId++; 164 | callbacks.put(callbackId, callback); 165 | } else { 166 | callbackId = 0; 167 | } 168 | RemoteInvocation invoke = new RemoteInvocation(methodName, params, callbackId); 169 | 170 | StreamWriter writer = new StreamWriter(__getSerializer()); 171 | 172 | try { 173 | writer.writeObject(invoke); 174 | 175 | JsArrayMixed workerData = writer.getWorkerData(); 176 | worker.postMessage(workerData, new ArrayBuffer[]{workerData.getObject(0)}); 177 | } catch (SerializationException e) { 178 | //TODO report, rethrow 179 | } 180 | 181 | } 182 | 183 | protected abstract void __invoke(String methodName, Object[] parameters); 184 | 185 | private void __checkLocal() { 186 | 187 | } 188 | } 189 | 190 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 7 8 | 9 | com.colinalworth.gwt.websockets 10 | gwt-websockets-parent 11 | 0.1.2-SNAPSHOT 12 | pom 13 | GWT Websockets parent project 14 | 15 | 2011 16 | 17 | 18 | Colin Alworth 19 | colin@colinalworth.com 20 | 21 | 22 | 23 | Vertispan LLC 24 | https://vertispan.com 25 | 26 | 27 | 28 | The Apache Software License, Version 2.0 29 | http://www.apache.org/licenses/LICENSE-2.0.txt 30 | 31 | 32 | 33 | scm:git:git://github.com/vertispan/some-gwt-module.git 34 | scm:git:ssh://github.com:vertispan/some-gwt-module.git 35 | https://github.com/vertispan/some-gwt-module/tree/master 36 | 37 | 38 | com.google.gwt 39 | org.codehaus.mojo 40 | 2.8.2 41 | 2.8.2 42 | 43 | 44 | 45 | 46 | 47 | ${gwt.groupId} 48 | gwt-servlet 49 | ${gwt.version} 50 | 51 | 52 | 54 | 55 | ${gwt.groupId} 56 | gwt-dev 57 | ${gwt.version} 58 | provided 59 | 60 | 61 | 62 | 63 | ${gwt.groupId} 64 | gwt-user 65 | ${gwt.version} 66 | 67 | 68 | 69 | junit 70 | junit 71 | 4.8.2 72 | test 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | ${gwt.plugin.groupId} 81 | gwt-maven-plugin 82 | ${gwt.plugin.version} 83 | 84 | 85 | ${gwt.groupId} 86 | gwt-user 87 | ${gwt.version} 88 | 89 | 90 | ${gwt.groupId} 91 | gwt-dev 92 | ${gwt.version} 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-compiler-plugin 99 | 2.3.2 100 | 101 | 1.8 102 | 1.8 103 | 104 | **/super/** 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.codehaus.mojo 113 | license-maven-plugin 114 | 1.4 115 | 116 | 117 | license-update 118 | generate-resources 119 | 120 | update-file-header 121 | add-third-party 122 | 123 | 124 | 125 | 126 | apache_v2 127 | false 128 | 129 | 130 | 131 | 132 | 133 | 134 | rpc-client-common 135 | 136 | javaee-websocket-gwt-rpc 137 | javaee-websocket-gwt-rpc-sample 138 | 139 | workers 140 | workers-sample 141 | 142 | 143 | 144 | 145 | 146 | ossrh 147 | https://oss.sonatype.org/content/repositories/snapshots 148 | 149 | 150 | ossrh 151 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 152 | 153 | 154 | 155 | 156 | 157 | 158 | release 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-source-plugin 164 | 2.2.1 165 | 166 | 167 | attach-sources 168 | 169 | jar-no-fork 170 | 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-javadoc-plugin 177 | 2.9.1 178 | 179 | 180 | attach-javadocs 181 | 182 | jar 183 | 184 | 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-gpg-plugin 191 | 1.5 192 | 193 | 194 | sign-artifacts 195 | verify 196 | 197 | sign 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /workers-sample/src/main/java/sharedchat/worker/client/AppWorker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers-sample 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package sharedchat.worker.client; 21 | 22 | import com.colinalworth.gwt.websockets.client.ServerBuilder; 23 | import com.colinalworth.gwt.worker.client.WorkerFactory; 24 | import com.colinalworth.gwt.worker.client.worker.MessagePort; 25 | import com.google.gwt.core.client.EntryPoint; 26 | import com.google.gwt.core.client.GWT; 27 | import jsinterop.annotations.JsFunction; 28 | import jsinterop.annotations.JsPackage; 29 | import jsinterop.annotations.JsProperty; 30 | import jsinterop.annotations.JsType; 31 | import sharedchat.common.client.ChatPage; 32 | import sharedchat.common.client.ChatWorker; 33 | import sharedchat.common.shared.ChatClient; 34 | import sharedchat.common.shared.ChatEvent; 35 | import sharedchat.common.shared.ChatJoin; 36 | import sharedchat.common.shared.ChatLeave; 37 | import sharedchat.common.shared.ChatMessage; 38 | import sharedchat.common.shared.ChatServer; 39 | 40 | import java.util.ArrayList; 41 | import java.util.Iterator; 42 | import java.util.List; 43 | import java.util.function.Consumer; 44 | 45 | /** 46 | * Shared/Service worker that will handle calls from the ui and make calls to the server. 47 | * It also keeps messages it has seen for later pages to request to catch up. In 48 | * theory at least, this should stash them away in localstorage or indexeddb, but 49 | * thats for another demo... 50 | */ 51 | public class AppWorker implements EntryPoint { 52 | 53 | //generated code 54 | public interface AppWorkerFactory extends WorkerFactory {} 55 | interface ChatServerBuilder extends ServerBuilder {} 56 | 57 | 58 | //factories to handle communication 59 | ChatServerBuilder serverBuilder = GWT.create(ChatServerBuilder.class); 60 | AppWorkerFactory pageCommunicationFactory = GWT.create(AppWorkerFactory.class); 61 | 62 | //app state 63 | private String username; 64 | private List events = new ArrayList<>(); 65 | private boolean connected; 66 | 67 | //pages that are probably still connected... 68 | private List connectedPages = new ArrayList<>(); 69 | 70 | private ChatServer serverConnection; 71 | 72 | @Override 73 | public void onModuleLoad() { 74 | serverBuilder.setPath("/chat"); 75 | serverConnection = serverBuilder.start(); 76 | 77 | self().setOnConnect(e -> { 78 | pageCommunicationFactory.wrapRemoteMessagePort(e.ports[0], new ChatWorker() { 79 | private ChatPage page; 80 | 81 | @Override 82 | public void login(String username) { 83 | if (AppWorker.this.username != null && !AppWorker.this.username.equals(username)) { 84 | //still logged in, signal logout first 85 | logout(); 86 | 87 | } 88 | //set the username, clear messages 89 | AppWorker.this.username = username; 90 | AppWorker.this.events.clear(); 91 | 92 | 93 | //tell the server we've joined 94 | serverConnection.login(username); 95 | 96 | //tell the pages we have a new username, clear messages 97 | broadcastToPages(page -> page.init(username, AppWorker.this.events)); 98 | } 99 | 100 | @Override 101 | public void logout() { 102 | broadcastToPages(page -> page.init(null, new ArrayList<>())); 103 | } 104 | 105 | @Override 106 | public void send(String message) { 107 | serverConnection.say(message); 108 | } 109 | 110 | @Override 111 | public void setRemote(ChatPage chatPage) { 112 | page = chatPage; 113 | connectedPages.add(chatPage); 114 | // on connect, inform the page of the messages so far and our username if any 115 | page.init(username, events); 116 | } 117 | 118 | @Override 119 | public ChatPage getRemote() { 120 | return page; 121 | } 122 | }); 123 | }); 124 | 125 | serverConnection.setClient(new ChatClient() { 126 | @Override 127 | public void onOpen() { 128 | //inform page of connection established 129 | 130 | connected = true; 131 | broadcastToPages(ChatPage::connected); 132 | } 133 | 134 | @Override 135 | public void onClose() { 136 | //try reconnect... 137 | //inform page of lost connection 138 | 139 | connected = false; 140 | broadcastToPages(ChatPage::disconnected); 141 | 142 | } 143 | 144 | @Override 145 | public void onError(Throwable error) { 146 | 147 | } 148 | 149 | @Override 150 | public void say(String username, String message) { 151 | handleEvent(new ChatMessage(username, message)); 152 | } 153 | 154 | @Override 155 | public void join(String username) { 156 | handleEvent(new ChatJoin(username)); 157 | } 158 | 159 | @Override 160 | public void part(String username) { 161 | handleEvent(new ChatLeave(username)); 162 | } 163 | }); 164 | } 165 | 166 | private void handleEvent(ChatEvent event) { 167 | broadcastToPages(event::handle); 168 | events.add(event); 169 | } 170 | 171 | private void broadcastToPages(UnsafeConsumer task) { 172 | for (Iterator iterator = connectedPages.iterator(); iterator.hasNext(); ) { 173 | ChatPage connectedPage = iterator.next(); 174 | try { 175 | task.acceptUnsafe(connectedPage); 176 | } catch (Exception e) { 177 | //disconnect 178 | iterator.remove(); 179 | } 180 | } 181 | } 182 | interface UnsafeConsumer extends Consumer { 183 | @Override 184 | default void accept(T t) { 185 | try { 186 | acceptUnsafe(t); 187 | } catch (Exception e) { 188 | if (e instanceof RuntimeException) { 189 | throw (RuntimeException) e; 190 | } 191 | throw new RuntimeException(e); 192 | } 193 | } 194 | 195 | void acceptUnsafe(T t) throws Exception; 196 | } 197 | 198 | private native SharedWorkerGlobalScope self() /*-{ 199 | return $wnd; 200 | }-*/; 201 | 202 | 203 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 204 | public interface SharedWorkerGlobalScope { 205 | @JsFunction 206 | public interface OnConnectCallback { 207 | void onConnect(Event event); 208 | 209 | } 210 | 211 | @JsProperty(name="onconnect") 212 | void setOnConnect(OnConnectCallback onconnect); 213 | 214 | @JsProperty(name="onconnect") 215 | OnConnectCallback getOnConnect(); 216 | } 217 | @JsType(isNative = true, namespace = JsPackage.GLOBAL) 218 | public static class Event { 219 | MessagePort[] ports; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /workers/src/main/java/com/colinalworth/gwt/worker/client/impl/StreamWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * workers 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.worker.client.impl; 21 | 22 | import com.colinalworth.gwt.worker.client.pako.Deflate; 23 | import com.google.gwt.core.client.JavaScriptObject; 24 | import com.google.gwt.core.client.JsArrayMixed; 25 | import com.google.gwt.typedarrays.shared.ArrayBuffer; 26 | import com.google.gwt.typedarrays.shared.ArrayBufferView; 27 | import com.google.gwt.user.client.rpc.SerializationException; 28 | import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter; 29 | import com.google.gwt.user.client.rpc.impl.Serializer; 30 | import playn.html.HasArrayBufferView; 31 | 32 | import java.nio.ByteBuffer; 33 | import java.nio.ByteOrder; 34 | import java.nio.FloatBuffer; 35 | import java.nio.IntBuffer; 36 | import java.util.List; 37 | 38 | /** 39 | * 40 | * 41 | * Header (int version, int flags, int payloadLength (empty)) 42 | * Payload (header then int[]) 43 | * StringTable (String[]) 44 | * 45 | * Basic worker format: 46 | * 47 | * [payload, stringtable] 48 | * 49 | * 50 | * For workers, we can actually push through the Header+Payload as a single arraybuffer and skip copying it, but the 51 | * StringTable has to be copied. 52 | * 53 | * 54 | * 55 | * Basic socket format: 56 | * version, flags, payloadLength, payload, stringtableLength, stringtable 57 | * 58 | * The last two are optional if there are no strings. 59 | * 60 | * Now we just have one array instead of two - all strings are gzipped and then concat'd into 61 | * 62 | */ 63 | public class StreamWriter extends AbstractSerializationStreamWriter { 64 | //constants from BigLongLibBase for long->int[] conversion 65 | protected static final int BITS = 22; 66 | protected static final int BITS01 = 2 * BITS; 67 | protected static final int BITS2 = 64 - BITS01; 68 | protected static final int MASK = (1 << BITS) - 1; 69 | protected static final int MASK_2 = (1 << BITS2) - 1; 70 | 71 | private ByteBuffer bb;//initial size 1kb 72 | private IntBuffer payload; 73 | 74 | private final Serializer serializer; 75 | 76 | 77 | public StreamWriter(Serializer serializer) { 78 | this.serializer = serializer; 79 | bb = ByteBuffer.allocate(8 << 2); 80 | bb.order(ByteOrder.nativeOrder()); 81 | payload = bb.asIntBuffer(); 82 | payload.position(3);//leave room for flags, version, size 83 | } 84 | 85 | 86 | 87 | public JsArrayMixed getWorkerData() { 88 | //shrink down to actual space used 89 | //TODO consider replacing with a List and make new ones as needed, then make a big one, or send them all 90 | // payload = payload.compact(); 91 | payload.limit(payload.position()); 92 | payload.position(0); 93 | payload = payload.slice(); 94 | 95 | payload.put(0, getFlags()); 96 | payload.put(1, getVersion()); 97 | //mark the size of the payload 98 | payload.put(2, payload.limit() - 3); 99 | 100 | JsArrayMixed value = JsArrayMixed.createArray().cast(); 101 | value.push(jso(((HasArrayBufferView) payload).getTypedArray().buffer())); 102 | value.push(jso(getStringTable().toArray(new String[0]))); 103 | 104 | payload = null;//finalized, can't go back 105 | return value; 106 | } 107 | 108 | public ArrayBufferView getSocketData() { 109 | // payload = payload.compact(); 110 | payload.limit(payload.position()); 111 | payload.position(0); 112 | payload = payload.slice(); 113 | 114 | //TODO don't compress if small enough! 115 | Deflate zip = new Deflate(); 116 | 117 | payload.put(0, getFlags()); 118 | payload.put(1, getVersion()); 119 | //mark the size of the payload 120 | payload.put(2, payload.limit() - 3); 121 | 122 | //shift bb to the same offsets as payload, then slice so we have a typed array with the right bounds 123 | bb.limit(payload.limit() << 2); 124 | bb.position(payload.position() << 2); 125 | bb = bb.slice();//http://thecodelesscode.com/case/209 126 | payload = null; 127 | 128 | List strings = getStringTable(); 129 | ArrayBufferView typedArray = ((HasArrayBufferView) bb).getTypedArray(); 130 | zip.push(typedArray, strings.isEmpty()); 131 | 132 | if (!strings.isEmpty()) { 133 | IntBuffer length = IntBuffer.allocate(1); 134 | length.put(0, strings.size()); 135 | ArrayBuffer lengthBuffer = ((HasArrayBufferView) length).getTypedArray().buffer(); 136 | zip.push(lengthBuffer, false); 137 | for (int i = 0; i < strings.size(); i++) { 138 | String string = strings.get(i); 139 | length.put(0, string.length()); 140 | zip.push(lengthBuffer, false); 141 | zip.push(string, i + 1 == strings.size()); 142 | } 143 | } 144 | 145 | return zip.getResult(); 146 | } 147 | 148 | private native JavaScriptObject jso(Object jso) /*-{ 149 | return jso; 150 | }-*/; 151 | private native JavaScriptObject jso(Object[] jso) /*-{ 152 | return jso.slice();//rebuild the array but without gwt's bookkeeping properties 153 | }-*/; 154 | 155 | @Override 156 | public String toString() { 157 | return "StreamWriter"; 158 | } 159 | 160 | @Override 161 | public void writeLong(long l) { 162 | //impl from BigLongLibBase 163 | int[] a = new int[3]; 164 | a[0] = (int) (l & MASK); 165 | a[1] = (int) ((l >> BITS) & MASK); 166 | a[2] = (int) ((l >> BITS01) & MASK_2); 167 | writeInt(a[0]); 168 | writeInt(a[1]); 169 | writeInt(a[2]); 170 | assert false : "longs not yet implemented"; 171 | } 172 | 173 | public void writeBoolean(boolean fieldValue) { 174 | writeInt(fieldValue ? 1 : 0);//wasteful, but no need to try to pack this way 175 | } 176 | 177 | @Override 178 | public void writeByte(byte fieldValue) { 179 | writeInt(fieldValue);//wasteful, but no need to try to pack this way 180 | } 181 | 182 | @Override 183 | public void writeChar(char ch) { 184 | writeInt(ch); 185 | } 186 | 187 | @Override 188 | public void writeFloat(float fieldValue) { 189 | maybeGrow(); 190 | bb.asFloatBuffer().put(payload.position(), fieldValue); 191 | payload.position(payload.position() + 1); 192 | } 193 | 194 | @Override 195 | public void writeDouble(double fieldValue) { 196 | writeLong(Double.doubleToLongBits(fieldValue)); 197 | } 198 | 199 | @Override 200 | public void writeInt(int fieldValue) { 201 | maybeGrow(); 202 | payload.put(fieldValue); 203 | } 204 | 205 | private void maybeGrow() { 206 | if (!payload.hasRemaining()) { 207 | ByteBuffer old = bb; 208 | bb = ByteBuffer.allocate(old.capacity() * 2); 209 | bb.order(ByteOrder.nativeOrder()); 210 | IntBuffer oldPayload = payload; 211 | payload = bb.asIntBuffer(); 212 | payload.put((IntBuffer)oldPayload.flip()); 213 | } 214 | } 215 | 216 | @Override 217 | public void writeShort(short value) { 218 | writeInt(value); 219 | } 220 | 221 | @Override 222 | protected void append(String s) { 223 | //strangely enough, we do nothing, and wait until we actually are asked to write the whole thing out 224 | } 225 | 226 | @Override 227 | protected String getObjectTypeSignature(Object o) throws SerializationException { 228 | Class clazz = o.getClass(); 229 | if(o instanceof Enum) { 230 | Enum e = (Enum)o; 231 | clazz = e.getDeclaringClass(); 232 | } 233 | 234 | return this.serializer.getSerializationSignature(clazz); 235 | } 236 | 237 | @Override 238 | protected void serialize(Object o, String s) throws SerializationException { 239 | this.serializer.serialize(this, o, s); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | (Name in flux, probably will be renamed to gwt-websockets) 2 | 3 | GWT library to make RPC calls over websockets. As in regular GWT-RPC, the client can call the server at 4 | any time, but with this library, the server can also call back the the client. 5 | 6 | WebSockets are established from the client to the server, and while open are maintained to allow them 7 | to continue to communicate over the same channel. Either side can send a string to the other at any time. 8 | This project uses this to enable one-way RPC methods to easily be sent. 9 | 10 | The default setup for this is to only use a pair of interfaces, one for the client, and one for the server. 11 | Since the client API needs to know about the server and vice versa, generics are used so that either side 12 | of the API can see to the other. 13 | 14 | Methods can optionally take a `Callback` object as the final parameter, but instead of the server 15 | implementing the same method synchronously, both client and server code are written to assume async behavior. 16 | Note however that callbacks are often not required - messages can be passed without *expecting* a reply back. 17 | Note also that callbacks are one-time use, and cannot be invoked multiple times - use a different method 18 | on the opposite interface to achieve that effect. 19 | 20 | [JSR-356](https://www.jcp.org/en/jsr/detail?id=356) is used presently as the only server-side implementation 21 | (the spec for javax.websocket, implemented by [Glassfish](https://tyrus.java.net/), 22 | [Jetty](http://www.eclipse.org/jetty/documentation/current/jetty-javaee.html#jetty-javaee-7), and 23 | [Tomcat](tomcat.apache.org/tomcat-7.0-doc/web-socket-howto.html)). The library uses version 1.0 of this 24 | API, as Jetty (and perhaps others) do not yet support 1.1. 25 | 26 | A new project has also been started adding rpc-like communication between web/shared/service workers. 27 | 28 | 29 | ## Example 30 | 31 | ### Client-Server Contract 32 | 33 | These interfaces refer to each other in their generics. Here is a simple client interface for a chat app: 34 | 35 | /** 36 | * Simple example of methods implemented by a GWT client that can be called from the server 37 | * 38 | */ 39 | public interface ChatClient extends Client { 40 | /** 41 | * Tells the client that a user posted a message to the chat room 42 | * @param username the user who sent the message 43 | * @param message the message the user sent 44 | */ 45 | void say(String username, String message); 46 | 47 | /** 48 | * Indicates that a new user has entered the chat room 49 | * @param username the user who logged in 50 | */ 51 | void join(String username); 52 | 53 | /** 54 | * Indicates that a user has left the chat room 55 | * @param username the user who left 56 | */ 57 | void part(String username); 58 | 59 | /** 60 | * Test method to have the server send the client a message and get a response right away. 61 | * This demonstrates that the server can call the client with a callback to get its response 62 | * other than via a Server method. 63 | * 64 | * @param callback response that the client should call upon receipt of this method 65 | */ 66 | void ping(Callback callback); 67 | } 68 | 69 | The client code must implement this interface, and will be called when the server invokes any of those 70 | methods. Once implemented, the client may do anything with these details - fire events to the rest of the 71 | app, call other methods, directly interact with the UI, etc. 72 | 73 | /** 74 | * Simple example of methods a server can have that can be invoked by a client. 75 | * 76 | */ 77 | @RemoteServiceRelativePath("/chat") 78 | public interface ChatServer extends Server { 79 | /** 80 | * Brings the user into the chat room, with the given username 81 | * @param username the name to use 82 | * @param callback indicates the login was successful, or passes back an error message 83 | */ 84 | void login(String username, Callback callback); 85 | 86 | /** 87 | * Sends the given message to the chatroom 88 | * @param message the message to say to the room 89 | */ 90 | void say(String message); 91 | } 92 | 93 | In this matching server interface, we can see the messages that any client can send to the server after 94 | connecting. The server in turn must implement this, and may call back to the client using any of the 95 | methods defined in the first interface. 96 | 97 | ### Client Wiring 98 | The client first builds an implementation of the client interface - this will be its way of recieving all 99 | 'callbacks' from the server. Then, we declare an interface to connect to the server: 100 | 101 | interface ChatServerBuilder extends ServerBuilder {} 102 | 103 | This interface can then be implemented with `GWT.create`, given a url and client instance, and the 104 | connection established: 105 | 106 | // Create an instance of the build 107 | final ChatServerBuilder builder = GWT.create(ChatServerBuilder.class); 108 | // Set the url to connect to - may or may not be the same as the original server 109 | builder.setUrl("ws://" + Window.Location.getHost() + "/chat"); 110 | 111 | // Get an instance of the client impl 112 | ChatClient impl = ...; 113 | 114 | // Start a connection to the server, and attach the client impl 115 | final ChatServer server = builder.start(); 116 | server.setClient(impl); 117 | 118 | Once the `onOpen()` method gets called on the client object, the connection is established, and the client 119 | may invoke any server method until `onClose()` is called. To restart the connection (or start another sim 120 | simultaneous connect), call `start()` again and then talk to the newly returned server object. 121 | 122 | The `AbstractClientImpl` class can serve as a handy base class, providing default implementations of the 123 | `onOpen()` and `onClose()` methods that fire events. 124 | 125 | ### Server Wiring 126 | 127 | With either API there is an `AbstractServerImpl` class. This provides the working details of the `Server` 128 | interface as well as the specifics how to interact with JSR-356. 129 | 130 | From within either client or server implementation, you can always get a reference to the other side - the 131 | server can call `getClient()`, and the client already has an instance (see below). Our ChatServerImpl 132 | probably needs to track all connected clients, so we'll start off with a field to hold them: 133 | 134 | public class ChatServerImpl extends AbstractServerImpl implements ChatServer { 135 | private final Map loggedIn = 136 | Collections.synchronizedMap(new HashMap()); 137 | 138 | Next, we'll want to implement each method - for example, when any user says something, we'll send it to all 139 | other connected users: 140 | 141 | @Override 142 | public void say(String message) { 143 | ChatClient c = getClient(); 144 | String userName = loggedIn.get(c); 145 | 146 | for (ChatClient connected : loggedIn.keySet()) { 147 | connected.say(userName, message); 148 | } 149 | } 150 | 151 | Note that this is not the only way to keep several clients in communication with each other, just one 152 | possible implementation, made deliberately simple for the sake of an example. For a larger solution, some 153 | kind of message queue could be used to send out messages to the proper recipients. 154 | 155 | ### JSR-356 156 | For the `javax.websocket` api, we have two basic options, as documented at http://docs.oracle.com/javaee/7/tutorial/doc/websocket.htm. 157 | The simplest way to do this is usually the [annotated approach](http://docs.oracle.com/javaee/7/tutorial/doc/websocket004.htm#BABFEBGA), 158 | by which the endpoint class must be decorated with `@ServerEndpoint()` to indicate the url it should be 159 | used to respond to. The other annotations are already present in the `RpcEndpoint` class (the superclass 160 | of `AbstractServerImpl`). 161 | 162 | @ServerEndpoint("/chat") 163 | public class ChatServerImpl extends AbstractServerImpl implements ChatServer { 164 | //... 165 | 166 | Check out the [javaee-websocket-gwt-rpc-sample](javaee-websocket-gwt-rpc-sample/) project for a working, 167 | runnable example of the above code. 168 | -------------------------------------------------------------------------------- /javaee-websocket-gwt-rpc/src/main/java/com/colinalworth/gwt/websockets/server/RpcEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * javaee-websocket-gwt-rpc 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.server; 21 | 22 | import com.colinalworth.gwt.websockets.shared.Client; 23 | import com.colinalworth.gwt.websockets.shared.Server; 24 | import com.colinalworth.gwt.websockets.shared.Server.Connection; 25 | import com.colinalworth.gwt.websockets.shared.impl.ClientCallbackInvocation; 26 | import com.colinalworth.gwt.websockets.shared.impl.ClientInvocation; 27 | import com.colinalworth.gwt.websockets.shared.impl.ServerCallbackInvocation; 28 | import com.colinalworth.gwt.websockets.shared.impl.ServerInvocation; 29 | import com.colinalworth.gwt.websockets.shared.impl.DummyRemoteService; 30 | import com.google.gwt.core.client.Callback; 31 | import com.google.gwt.user.client.rpc.SerializationException; 32 | import com.google.gwt.user.server.rpc.RPC; 33 | import com.google.gwt.user.server.rpc.RPCRequest; 34 | import com.google.gwt.user.server.rpc.SerializationPolicy; 35 | import com.google.gwt.user.server.rpc.SerializationPolicyProvider; 36 | 37 | import javax.websocket.OnClose; 38 | import javax.websocket.OnError; 39 | import javax.websocket.OnMessage; 40 | import javax.websocket.OnOpen; 41 | import javax.websocket.Session; 42 | import java.io.IOException; 43 | import java.lang.reflect.InvocationHandler; 44 | import java.lang.reflect.InvocationTargetException; 45 | import java.lang.reflect.Method; 46 | import java.lang.reflect.Proxy; 47 | import java.util.Collections; 48 | import java.util.HashMap; 49 | import java.util.Map; 50 | import java.util.concurrent.atomic.AtomicInteger; 51 | 52 | public class RpcEndpoint, C extends Client> { 53 | private static final Method INVOKE_RPC_METHOD; 54 | private static final Method CALLBACK_RPC_METHOD; 55 | static { 56 | Method m1 = null, m2 = null; 57 | try { 58 | m1 = DummyRemoteService.class.getDeclaredMethod("invoke", ServerInvocation.class); 59 | m2 = DummyRemoteService.class.getDeclaredMethod("callback", ServerCallbackInvocation.class); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | INVOKE_RPC_METHOD = m1; 64 | CALLBACK_RPC_METHOD = m2; 65 | } 66 | 67 | private final S server; 68 | private final Class clientType; 69 | 70 | private final Map cachedMethods = Collections.synchronizedMap(new HashMap()); 71 | 72 | 73 | private final Map> callbacks = Collections.synchronizedMap(new HashMap>()); 74 | private final AtomicInteger nextCallbackId = new AtomicInteger(1); 75 | 76 | public RpcEndpoint(S server, Class client) { 77 | assert server != null : "Can't make an endpoint with a null server instance"; 78 | this.server = server; 79 | this.clientType = client; 80 | } 81 | 82 | //default accessible to let AbstractServerImpl call it 83 | RpcEndpoint(Class client) { 84 | this.server = (S) this; 85 | this.clientType = client; 86 | } 87 | 88 | @OnOpen 89 | public void onOpen(final Session session) { 90 | assert server.getClient() == null; 91 | //make the client proxy, attach to session instance for future use 92 | //TODO 93 | C client = (C) Proxy.newProxyInstance(clientType.getClassLoader(), new Class[]{clientType}, new IH(session)); 94 | server.setClient(client); 95 | server.onOpen(new Jsr356Connection(session), server.getClient()); 96 | 97 | //listen for future incoming calls (commented out since using annotated approach instead) 98 | // session.addMessageHandler(new MessageHandler.Whole() { 99 | // @Override 100 | // public void onMessage(String message) { 101 | // handleMessage(session, message); 102 | // } 103 | // }); 104 | session.setMaxIdleTimeout(0); 105 | } 106 | 107 | @OnMessage 108 | public void handleMessage(Session session, String message) { 109 | //TODO hopefully this isn't *actually* required for all containers, but seems needed for jetty 9 110 | Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); 111 | 112 | RPCRequest req = RPC.decodeRequest(message, null, makeProvider()); 113 | //Method m = req.getMethod();//garbage 114 | 115 | Object object = req.getParameters()[0]; 116 | 117 | if (object instanceof ServerCallbackInvocation) { 118 | ServerCallbackInvocation callbackInvoke = (ServerCallbackInvocation) object; 119 | 120 | if (callbacks.containsKey(callbackInvoke.getCallbackId())) { 121 | @SuppressWarnings("unchecked") 122 | Callback callback = (Callback) callbacks.remove(callbackInvoke.getCallbackId()); 123 | 124 | if (callbackInvoke.isSuccess()) { 125 | callback.onSuccess(callbackInvoke.getResponse()); 126 | } else { 127 | callback.onFailure(callbackInvoke.getResponse()); 128 | } 129 | } 130 | return; 131 | } 132 | 133 | ServerInvocation invoke = (ServerInvocation) object; 134 | //TODO verify the method to be invoked 135 | 136 | Method method; 137 | //TODO this caching probably doesn't make sense for jsr356 since we get a new instance per connection 138 | synchronized (cachedMethods) { 139 | method = cachedMethods.get(invoke.getMethod()); 140 | if (method == null) { 141 | for (Method m : server.getClass().getMethods()) { 142 | if (m.getName().equals(invoke.getMethod())) { 143 | //TODO check better than this, verify the method may be invoked from the client 144 | method = m; 145 | break; 146 | } 147 | } 148 | if (method == null) { 149 | //TODO throw something 150 | } else { 151 | cachedMethods.put(invoke.getMethod(), method); 152 | } 153 | } 154 | } 155 | 156 | try { 157 | if (invoke.getCallbackId() != 0) { 158 | Object[] args = addCallbackToArgs(invoke.getParameters(), invoke.getCallbackId(), session); 159 | method.invoke(server, args); 160 | } else { 161 | method.invoke(server, invoke.getParameters()); 162 | } 163 | } catch (InvocationTargetException ex) { 164 | Throwable unwrapped = ex.getCause(); 165 | server.onError(unwrapped); 166 | } catch (IllegalAccessException e) { 167 | server.onError(e); 168 | } 169 | } 170 | 171 | private Object[] addCallbackToArgs(Object[] originalArgs, final int callbackId, final Session session) { 172 | Object[] newArgs = new Object[originalArgs.length + 1]; 173 | System.arraycopy(originalArgs, 0, newArgs, 0, originalArgs.length); 174 | newArgs[originalArgs.length] = new Callback() { 175 | boolean fired = false; 176 | @Override 177 | public void onSuccess(Object result) { 178 | checkFired(); 179 | callback(callbackId, result, true, session); 180 | } 181 | 182 | @Override 183 | public void onFailure(Object reason) { 184 | checkFired(); 185 | callback(callbackId, reason, false, session); 186 | } 187 | 188 | private void checkFired() { 189 | if (fired) { 190 | throw new IllegalStateException("Callback already used, cannot be used again."); 191 | } 192 | fired = true; 193 | } 194 | }; 195 | return newArgs; 196 | } 197 | 198 | private void callback(int callbackId, Object response, boolean isSuccess, Session session) { 199 | ClientCallbackInvocation callbackInvocation = new ClientCallbackInvocation(callbackId, response, isSuccess); 200 | 201 | try { 202 | // Encode and send the message 203 | int flags = 0; 204 | String message = RPC.encodeResponseForSuccess(CALLBACK_RPC_METHOD, callbackInvocation, makePolicy(), flags); 205 | session.getAsyncRemote().sendText(message); 206 | } catch (SerializationException e) { 207 | throw new RuntimeException("Failed to send callback to client, " + e); 208 | } 209 | 210 | } 211 | 212 | @OnClose 213 | public void onClose(Session session) { 214 | assert server.getClient() != null; 215 | server.onClose(new Jsr356Connection(session), server.getClient()); 216 | } 217 | 218 | @OnError 219 | public void onError(Throwable thr) { 220 | if (server != this) { 221 | server.onError(thr); 222 | } 223 | } 224 | 225 | protected static SerializationPolicy makePolicy() { 226 | return new SerializationPolicy() { 227 | @Override 228 | public void validateSerialize(Class clazz) throws SerializationException { 229 | } 230 | @Override 231 | public void validateDeserialize(Class clazz) 232 | throws SerializationException { 233 | } 234 | @Override 235 | public boolean shouldSerializeFields(Class clazz) { 236 | return clazz != null; 237 | } 238 | @Override 239 | public boolean shouldDeserializeFields(Class clazz) { 240 | return clazz != null; 241 | } 242 | public boolean shouldSerializeFinalFields() { 243 | return true; 244 | } 245 | }; 246 | } 247 | protected static SerializationPolicyProvider makeProvider() { 248 | return new SerializationPolicyProvider() { 249 | @Override 250 | public SerializationPolicy getSerializationPolicy(String moduleBaseURL, 251 | String serializationPolicyStrongName) { 252 | return makePolicy(); 253 | } 254 | }; 255 | } 256 | 257 | private class IH implements InvocationHandler { 258 | private final Session session; 259 | 260 | private IH(Session session) { 261 | this.session = session; 262 | } 263 | 264 | @Override 265 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 266 | // First, directly call methods defined on Object (is this even needed?) 267 | if (method.getDeclaringClass() == Object.class) { 268 | return method.invoke(this, args); 269 | } 270 | // Then, make sure the server isn't calling client bookkeeping methods 271 | if (method.getDeclaringClass() == Client.class) { 272 | throw new IllegalStateException("This method is only intended to be called on the client itself"); 273 | } 274 | //TODO work this over ahead of time, dont make a runtime check 275 | if (method.getReturnType() != void.class && method.getReturnType() != Void.class) { 276 | throw new IllegalArgumentException("Calls to client must be of return type Void"); 277 | } 278 | 279 | final int callbackId; 280 | final Object[] actualArgs; 281 | if (method.getParameterTypes().length > 0 && method.getParameterTypes()[method.getParameterTypes().length - 1] == Callback.class) { 282 | callbackId = nextCallbackId.getAndIncrement(); 283 | callbacks.put(callbackId, (Callback) args[args.length - 1]); 284 | actualArgs = new Object[args.length - 1]; 285 | System.arraycopy(args, 0, actualArgs, 0, actualArgs.length); 286 | } else { 287 | callbackId = 0; 288 | actualArgs = args; 289 | } 290 | 291 | // Build an invocation instance to send to the client 292 | ClientInvocation invocation = new ClientInvocation(method.getName(), actualArgs, callbackId); 293 | 294 | // Encode and send the message 295 | int flags = 0; 296 | String message = RPC.encodeResponseForSuccess(INVOKE_RPC_METHOD, invocation, makePolicy(), flags); 297 | 298 | session.getAsyncRemote().sendText(message); 299 | //TODO handle future's error, if any 300 | 301 | 302 | //all methods are voids, so no return value 303 | return null; 304 | } 305 | } 306 | 307 | private static class Jsr356Connection implements Connection { 308 | private final Session session; 309 | 310 | private Jsr356Connection(Session session) { 311 | this.session = session; 312 | } 313 | 314 | @Override 315 | public void data(String key, Object value) { 316 | session.getUserProperties().put(key, value); 317 | } 318 | 319 | @Override 320 | public Object data(String key) { 321 | return session.getUserProperties().get(key); 322 | } 323 | 324 | @Override 325 | public void close() { 326 | try { 327 | session.close(); 328 | } catch (IOException e) { 329 | e.printStackTrace(); 330 | } 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /rpc-client-common/src/main/java/com/colinalworth/gwt/websockets/rebind/ServerCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * rpc-client-common 4 | * %% 5 | * Copyright (C) 2011 - 2018 Vertispan LLC 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.colinalworth.gwt.websockets.rebind; 21 | 22 | import com.colinalworth.gwt.websockets.client.impl.ServerImpl; 23 | import com.colinalworth.gwt.websockets.shared.Client; 24 | import com.colinalworth.gwt.websockets.shared.Server; 25 | import com.colinalworth.gwt.websockets.shared.impl.ClientCallbackInvocation; 26 | import com.colinalworth.gwt.websockets.shared.impl.ClientInvocation; 27 | import com.colinalworth.gwt.websockets.shared.impl.ServerCallbackInvocation; 28 | import com.colinalworth.gwt.websockets.shared.impl.ServerInvocation; 29 | import com.colinalworth.gwt.websockets.client.ServerBuilder; 30 | import com.google.gwt.core.client.Callback; 31 | import com.google.gwt.core.client.GWT; 32 | import com.google.gwt.core.ext.GeneratorContext; 33 | import com.google.gwt.core.ext.TreeLogger; 34 | import com.google.gwt.core.ext.TreeLogger.Type; 35 | import com.google.gwt.core.ext.UnableToCompleteException; 36 | import com.google.gwt.core.ext.typeinfo.JClassType; 37 | import com.google.gwt.core.ext.typeinfo.JMethod; 38 | import com.google.gwt.core.ext.typeinfo.JParameter; 39 | import com.google.gwt.core.ext.typeinfo.TypeOracle; 40 | import com.google.gwt.dev.util.Name; 41 | import com.google.gwt.editor.rebind.model.ModelUtils; 42 | import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; 43 | import com.google.gwt.user.client.rpc.impl.Serializer; 44 | import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; 45 | import com.google.gwt.user.rebind.SourceWriter; 46 | import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder; 47 | import com.google.gwt.user.rebind.rpc.TypeSerializerCreator; 48 | 49 | import java.io.PrintWriter; 50 | 51 | /** 52 | * Creates a new instance of the given Server type 53 | * 54 | */ 55 | public class ServerCreator { 56 | private final JClassType serverType; 57 | /** 58 | * 59 | */ 60 | public ServerCreator(JClassType serverType) { 61 | this.serverType = serverType; 62 | } 63 | 64 | public void create(TreeLogger logger, GeneratorContext context) throws UnableToCompleteException { 65 | String typeName = this.serverType.getQualifiedSourceName(); 66 | 67 | String packageName = getPackageName(); 68 | String simpleName = getSimpleName(); 69 | 70 | TypeOracle oracle = context.getTypeOracle(); 71 | 72 | PrintWriter pw = context.tryCreate(logger, packageName, simpleName); 73 | if (pw == null) { 74 | return; 75 | } 76 | JClassType serverType = oracle.findType(Name.getSourceNameForClass(Server.class)); 77 | JClassType clientType = ModelUtils.findParameterizationOf(serverType, this.serverType)[1]; 78 | 79 | ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(packageName, simpleName); 80 | factory.setSuperclass(Name.getSourceNameForClass(ServerImpl.class) + "<" + typeName + "," + clientType.getQualifiedSourceName() + ">"); 81 | factory.addImplementedInterface(typeName); 82 | 83 | SourceWriter sw = factory.createSourceWriter(context, pw); 84 | 85 | //TODO move this check before the printwriter creation can fail, and allow the warn to be optional 86 | sw.println("public %1$s(%2$s errorHandler) {", simpleName, Name.getSourceNameForClass(ServerBuilder.ConnectionErrorHandler.class)); 87 | RemoteServiceRelativePath path = this.serverType.getAnnotation(RemoteServiceRelativePath.class); 88 | if (path == null) { 89 | // logger.log(Type.WARN, "@RemoteServiceRelativePath required on " + typeName + " to make a connection to the server without a ServerBuilder"); 90 | // throw new UnableToCompleteException(); 91 | sw.indentln("super(null);"); 92 | sw.indentln("throw new RuntimeException(\"@RemoteServiceRelativePath annotation required on %1$s to make a connection without a path defined in ServerBuilder\");"); 93 | } else { 94 | sw.indentln("super(errorHandler, " + 95 | "com.google.gwt.user.client.Window.Location.getProtocol().toLowerCase().startsWith(\"https\") ? \"wss://\": \"ws://\", " + 96 | "com.google.gwt.user.client.Window.Location.getHost(), \"%1$s\");", path.value()); 97 | } 98 | sw.println("}"); 99 | 100 | sw.println("public %1$s(%2$s errorHandler, String url) {", simpleName, Name.getSourceNameForClass(ServerBuilder.ConnectionErrorHandler.class)); 101 | sw.indentln("super(errorHandler, url);"); 102 | sw.println("}"); 103 | 104 | //Find all types that may go over the wire 105 | // Collect the types the server will send to the client using the Client interface 106 | SerializableTypeOracleBuilder serverSerializerBuilder = new SerializableTypeOracleBuilder(logger, context); 107 | appendMethodParameters(logger, clientType, Client.class, serverSerializerBuilder); 108 | appendCallbackParameters(logger, this.serverType, Server.class, serverSerializerBuilder); 109 | // Also add the wrapper object ClientInvocation 110 | serverSerializerBuilder.addRootType(logger, oracle.findType(ClientInvocation.class.getName())); 111 | serverSerializerBuilder.addRootType(logger, oracle.findType(ClientCallbackInvocation.class.getName())); 112 | 113 | // Collect the types the client will send to the server using the Server interface 114 | SerializableTypeOracleBuilder clientSerializerBuilder = new SerializableTypeOracleBuilder(logger, context); 115 | appendMethodParameters(logger, this.serverType, Server.class, clientSerializerBuilder); 116 | appendCallbackParameters(logger, clientType, Client.class, clientSerializerBuilder); 117 | // Also add the ServerInvocation wrapper 118 | clientSerializerBuilder.addRootType(logger, oracle.findType(ServerInvocation.class.getName())); 119 | clientSerializerBuilder.addRootType(logger, oracle.findType(ServerCallbackInvocation.class.getName())); 120 | 121 | String tsName = simpleName + "_TypeSerializer"; 122 | TypeSerializerCreator serializerCreator = new TypeSerializerCreator(logger, clientSerializerBuilder.build(logger), serverSerializerBuilder.build(logger), context, packageName + "." + tsName, tsName); 123 | serializerCreator.realize(logger); 124 | 125 | // Make the newly created Serializer available at runtime 126 | sw.println("protected %1$s __getSerializer() {", Serializer.class.getName()); 127 | sw.indentln("return %2$s.<%1$s>create(%1$s.class);", tsName, GWT.class.getName()); 128 | sw.println("}"); 129 | 130 | // Build methods that call from the client to the server 131 | for (JMethod m : this.serverType.getInheritableMethods()) { 132 | if (isRemoteMethod(m, Server.class)) { 133 | printServerMethodBody(logger, context, sw, m); 134 | } 135 | } 136 | 137 | 138 | // Read incoming calls and dispatch them to the correct client method 139 | sw.println("protected void __invoke(String method, Object[] params) {"); 140 | for (JMethod m : clientType.getInheritableMethods()) { 141 | if (isRemoteMethod(m, Client.class)) { 142 | JParameter[] params = m.getParameters(); 143 | sw.println("if (method.equals(\"%1$s\") && params.length == %2$d) {", m.getName(), params.length); 144 | sw.indent(); 145 | sw.println("getClient().%1$s(", m.getName()); 146 | sw.indent(); 147 | for (int i = 0; i < params.length; i++) { 148 | if (i != 0) { 149 | sw.print(","); 150 | } 151 | sw.println("(%1$s)params[%2$d]", params[i].getType().getQualifiedSourceName(), i); 152 | } 153 | sw.outdent(); 154 | sw.println(");"); 155 | sw.outdent(); 156 | sw.println("}"); 157 | } 158 | } 159 | sw.println("}"); 160 | 161 | 162 | sw.println("protected void __onError(Exception error) {"); 163 | //TODO 164 | sw.println("}"); 165 | 166 | sw.commit(logger); 167 | } 168 | 169 | 170 | public String getPackageName() { 171 | return serverType.getPackage().getName(); 172 | } 173 | public String getSimpleName() { 174 | return serverType.getName().replace('.', '_') + "_ProxyImpl"; 175 | } 176 | 177 | public String getQualifiedSourceName() { 178 | return getPackageName() + "." + getSimpleName(); 179 | } 180 | 181 | 182 | 183 | /** 184 | * Helper method to build up the list of types that can go over the wire 185 | * @param logger 186 | * @param serviceInterface 187 | * @param serviceSuperClass 188 | * @param serializerBuilder 189 | */ 190 | private void appendMethodParameters(TreeLogger logger, JClassType serviceInterface, Class serviceSuperClass, SerializableTypeOracleBuilder serializerBuilder) { 191 | TreeLogger l = logger.branch(Type.DEBUG, "Adding params types to " + serviceInterface.getName()); 192 | for (JMethod m : serviceInterface.getMethods()) { 193 | if (isRemoteMethod(m, serviceSuperClass)) { 194 | JParameter[] parameters = m.getParameters(); 195 | for (int i = 0; i < parameters.length; i++) { 196 | JParameter param = parameters[i]; 197 | if (i + 1 != m.getParameters().length || param.getType().isInterface() == null || !param.getType().isInterface().getQualifiedSourceName().equals(Callback.class.getName())) { 198 | serializerBuilder.addRootType(l, param.getType()); 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | private void appendCallbackParameters(TreeLogger logger, JClassType serviceInterface, Class serviceSuperClass, SerializableTypeOracleBuilder serializerBuilder) { 206 | TreeLogger l = logger.branch(Type.DEBUG, "Adding callback types to" + serviceInterface.getName()); 207 | for (JMethod m : serviceInterface.getMethods()) { 208 | if (isRemoteMethod(m, serviceSuperClass)) { 209 | JParameter[] parameters = m.getParameters(); 210 | for (int i = 0; i < parameters.length; i++) { 211 | JParameter param = parameters[i]; 212 | if (i + 1 == m.getParameters().length && param.getType().isInterface() != null && param.getType().isInterface().getQualifiedSourceName().equals(Callback.class.getName())) { 213 | //read generics from Callback and add as root types 214 | JClassType t = param.getType().isParameterized().getTypeArgs()[0]; 215 | JClassType f = param.getType().isParameterized().getTypeArgs()[1]; 216 | serializerBuilder.addRootType(l, t); 217 | serializerBuilder.addRootType(l, f); 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | /** 225 | * Writes out the method to use to invoke a server call. Mostly derived from RPC's way of building proxy methods 226 | * 227 | * @param logger 228 | * @param context 229 | * @param sw 230 | * @param m 231 | */ 232 | private void printServerMethodBody(TreeLogger logger, GeneratorContext context, SourceWriter sw, JMethod m) { 233 | sw.println("%1$s {", m.getReadableDeclaration(false, true, true, true, true)); 234 | sw.indent(); 235 | 236 | sw.print("__sendMessage(\"%1$s\"", m.getName()); 237 | StringBuilder sb = new StringBuilder(); 238 | String callback = "null"; 239 | JParameter[] parameters = m.getParameters(); 240 | for (int i = 0; i < parameters.length; i++) { 241 | JParameter param = parameters[i]; 242 | if (i + 1 == parameters.length && param.getType().isInterface() != null && param.getType().isInterface().getQualifiedSourceName().equals(Callback.class.getName())) { 243 | callback = param.getName(); 244 | } else { 245 | sb.append(",\n").append(param.getName()); 246 | } 247 | } 248 | sw.print(","); 249 | sw.println(callback); 250 | sw.print(sb.toString()); 251 | sw.println(");"); 252 | 253 | sw.outdent(); 254 | sw.println("}"); 255 | } 256 | 257 | /** 258 | * Checks to see if the given method can be called over the wire. 259 | * 260 | * 261 | * @param m method to check 262 | * @param superClass either {@link Server} or {@link Client}, indicating which direction the call will be made 263 | * @return 264 | */ 265 | private boolean isRemoteMethod(JMethod m, Class superClass) { 266 | assert superClass == Server.class || superClass == Client.class; 267 | return !m.getEnclosingType().getQualifiedSourceName().equals(superClass.getName()); 268 | } 269 | } 270 | --------------------------------------------------------------------------------