ar) {
35 | if (ar.failed()) {
36 | startedResult.setFailure(ar.cause());
37 | } else if (ar.succeeded()) {
38 | startedResult.setResult(null);
39 | }
40 | }
41 | });
42 | new ChannelBridge(vertx, config).setHook(new BridgeHook(vertx)).bridge(countDownLatch);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/objc/ObjCPlatform.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.objc;
15 |
16 | import com.goodow.realtime.core.Diff;
17 | import com.goodow.realtime.core.Net;
18 | import com.goodow.realtime.core.Platform;
19 | import com.goodow.realtime.core.Platform.Type;
20 | import com.goodow.realtime.core.PlatformFactory;
21 | import com.goodow.realtime.core.Scheduler;
22 |
23 | class ObjCPlatform implements PlatformFactory {
24 | /**
25 | * Registers the Objective-C platform with a default configuration.
26 | */
27 | public static void register() {
28 | Platform.setFactory(new ObjCPlatform());
29 | }
30 |
31 | /*-[
32 | + (void)load {
33 | [ComGoodowRealtimeObjcObjCPlatform register__];
34 | }
35 | ]-*/;
36 |
37 | private final Net net = new ObjCNet();
38 | private final ObjCScheduler scheduler = new ObjCScheduler();
39 | private final ObjCDiff diff = new ObjCDiff();
40 |
41 | @Override
42 | public Diff diff() {
43 | return diff;
44 | }
45 |
46 | @Override
47 | public Net net() {
48 | return net;
49 | }
50 |
51 | @Override
52 | public Scheduler scheduler() {
53 | return scheduler;
54 | }
55 |
56 | @Override
57 | public Type type() {
58 | return Type.IOS;
59 | }
60 | }
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .SUFFIXES: .java .m
2 | .PHONY: default clean translate link
3 |
4 | include ../resources/make/common.mk
5 | # J2OBJC_DIST = GDChannel/Project/Pods/J2ObjC/dist
6 |
7 | CHANNEL_GEN_DIR = GDChannel/Classes/generated
8 | MAIN_SOURCES = $(subst $(MAIN_SRC_DIR)/,,$(shell find $(MAIN_SRC_DIR) -name *.java ! -path "*/html/*" ! -path "*/server/*"))
9 | MAIN_GEN_SOURCES = $(MAIN_SOURCES:%.java=$(CHANNEL_GEN_DIR)/%.m)
10 | OVERRIDE_GEN_DIR = GDChannel/Classes/override
11 | MAIN_OBJECTS = $(MAIN_SOURCES:%.java=$(BUILD_DIR)/main/%.o)
12 | SUPPORT_LIB = $(BUILD_DIR)/libGDChannel.a
13 |
14 | TEMP_PATH = $(M2_REPO)/com/goodow/realtime/realtime-json/0.5.5-SNAPSHOT/realtime-json-0.5.5-SNAPSHOT.jar
15 | CLASSPATH = $(shell echo $(TEMP_PATH) | sed 's/ //g')
16 |
17 | default: clean translate
18 |
19 | translate: translate_main
20 |
21 | pre_translate_main: $(BUILD_DIR) $(CHANNEL_GEN_DIR)
22 | @rm -f $(MAIN_SOURCE_LIST)
23 | @touch $(MAIN_SOURCE_LIST)
24 |
25 | $(CHANNEL_GEN_DIR)/%.m $(CHANNEL_GEN_DIR)/%.h: $(MAIN_SRC_DIR)/%.java
26 | @echo $? >> $(MAIN_SOURCE_LIST)
27 |
28 | translate_main: pre_translate_main $(MAIN_GEN_SOURCES)
29 | @if [ `cat $(MAIN_SOURCE_LIST) | wc -l` -ge 1 ] ; then \
30 | $(J2OBJC) -sourcepath $(MAIN_SRC_DIR) -d $(CHANNEL_GEN_DIR) \
31 | -classpath $(CLASSPATH) \
32 | `cat $(MAIN_SOURCE_LIST)` ; \
33 | fi
34 | cp -r $(OVERRIDE_GEN_DIR)/ $(CHANNEL_GEN_DIR)
35 |
36 | $(BUILD_DIR)/main/%.o: $(CHANNEL_GEN_DIR)/%.m $(MAIN_SRC_DIR)/%.java
37 | @mkdir -p `dirname $@`
38 | @$(J2OBJCC) -c $< -o $@ -g -I$(CHANNEL_GEN_DIR)
39 |
40 | $(SUPPORT_LIB): $(MAIN_OBJECTS)
41 | libtool -static -o $(SUPPORT_LIB) $(MAIN_OBJECTS) $(SUPPORT_LIB)
42 |
43 | link: translate $(SUPPORT_LIB)
44 |
45 | $(CHANNEL_GEN_DIR):
46 | @mkdir -p $(CHANNEL_GEN_DIR)
47 | $(BUILD_DIR):
48 | @mkdir -p $(BUILD_DIR)/main
49 |
50 | clean:
51 | @rm -rf $(CHANNEL_GEN_DIR) $(BUILD_DIR)
52 |
--------------------------------------------------------------------------------
/src/main/java/com/google/gwt/core/client/js/JsProperty.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 | package com.google.gwt.core.client.js;
17 |
18 | import java.lang.annotation.Documented;
19 | import java.lang.annotation.ElementType;
20 | import java.lang.annotation.Retention;
21 | import java.lang.annotation.RetentionPolicy;
22 | import java.lang.annotation.Target;
23 |
24 | /**
25 | * JsProperty marks a method in a {@link com.google.gwt.core.client.js.JsType} as a property accessor and recognizes
26 | * JavaBean style naming convention. Instead of translating method calls to JsProperty methods
27 | * as method calls in JS, they will be replaced with dotted property lookups.
28 | * Examples:
29 | *
30 | * {@code @JsProperty getX()} translates as this.x
31 | * {@code @JsProperty x()} translates as this.x
32 | * {@code @JsProperty setX(int y)} translates as this.x=y
33 | * {@code @JsProperty x(int x)} translates as this.x=y
34 | * {@code @JsProperty hasX(int x)} translates as x in this
35 | *
36 | *
37 | * In addition, fluent style return this syntax is supported for setters, so
38 | * {@code @JsProperty T setX(int x)} translates as this.x=x, return this .
39 | */
40 | @Retention(RetentionPolicy.RUNTIME)
41 | @Target(ElementType.METHOD)
42 | @Documented
43 | public @interface JsProperty {
44 | String value() default "";
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/core/Scheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.core;
15 |
16 | /**
17 | * This class provides low-level task scheduling primitives.
18 | */
19 | public interface Scheduler {
20 | /**
21 | * Cancel the timer with the specified {@code id}. Returns {@code} true if the timer was
22 | * successfully cancelled, or {@code false} if the timer does not exist.
23 | */
24 | boolean cancelTimer(int id);
25 |
26 | void handle(Object handler, Object event);
27 |
28 | /**
29 | * A deferred command is executed after the event loop returns.
30 | */
31 | void scheduleDeferred(Handler handler);
32 |
33 | /**
34 | * Set a one-shot timer to fire after {@code delayMs} milliseconds, at which point {@code handler}
35 | * will be called.
36 | *
37 | * @return the unique ID of the timer
38 | */
39 | int scheduleDelay(int delayMs, Handler handler);
40 |
41 | /**
42 | * Schedules a repeating handler that is scheduled with a constant periodicity. That is, the
43 | * handler will be invoked every delayMs milliseconds, regardless of how long the
44 | * previous invocation took to complete.
45 | *
46 | * @param delayMs the period with which the handler is executed
47 | * @param handler the handler to execute
48 | *
49 | * @return the unique ID of the timer
50 | */
51 | int schedulePeriodic(int delayMs, Handler handler);
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/JavaDiff.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.core.Diff;
17 | import com.goodow.realtime.json.JsonArray;
18 |
19 | import java.util.Comparator;
20 | import java.util.LinkedList;
21 |
22 | import name.fraser.neil.plaintext.diff_match_patch;
23 |
24 | public class JavaDiff implements Diff {
25 | private final diff_match_patch dmp = new diff_match_patch();
26 |
27 | @Override
28 | public void diff(String before, String after, ListTarget target) {
29 | LinkedList diffs = dmp.diff_main(before, after);
30 | dmp.diff_cleanupSemantic(diffs);
31 | int cursor = 0;
32 | for (diff_match_patch.Diff diff : diffs) {
33 | String text = diff.text;
34 | int len = text.length();
35 | switch (diff.operation) {
36 | case EQUAL:
37 | cursor += len;
38 | break;
39 | case INSERT:
40 | target.insert(cursor, text);
41 | cursor += len;
42 | break;
43 | case DELETE:
44 | target.remove(cursor, len);
45 | break;
46 | default:
47 | throw new RuntimeException("Shouldn't reach here!");
48 | }
49 | }
50 | }
51 |
52 | @Override
53 | public void diff(JsonArray before, JsonArray after, ListTarget target,
54 | Comparator comparator) {
55 |
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/core/Platform.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.core;
15 |
16 | /**
17 | * The main Platform interface. The static methods in this class provide access to the various
18 | * available subsystems.
19 | *
20 | *
21 | * You must register a {@link Platform} before calling any of these methods. For example,
22 | * JavaPlatform.register();.
23 | *
24 | */
25 | public class Platform {
26 | public enum Type {
27 | JAVA, HTML, ANDROID, IOS, FLASH, STUB, VERTX
28 | }
29 |
30 | private static PlatformFactory FACTORY;
31 |
32 | public static Diff diff() {
33 | return get().diff();
34 | }
35 |
36 | public static Net net() {
37 | return get().net();
38 | }
39 |
40 | public static Scheduler scheduler() {
41 | return get().scheduler();
42 | }
43 |
44 | /**
45 | * Configures the current {@link Platform}. Do not call this directly unless you're implementing a
46 | * new platform.
47 | */
48 | public static void setFactory(PlatformFactory factory) {
49 | FACTORY = factory;
50 | }
51 |
52 | public static Platform.Type type() {
53 | return get().type();
54 | }
55 |
56 | private static PlatformFactory get() {
57 | assert FACTORY != null :
58 | "You must register a platform first by invoke {Java|Android}Platform.register()";
59 | return FACTORY;
60 | }
61 |
62 | // Non-instantiable
63 | protected Platform() {
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/html/HtmlDiff.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.html;
15 |
16 | import com.goodow.realtime.core.Diff;
17 | import com.goodow.realtime.json.JsonArray;
18 |
19 | import java.util.Comparator;
20 |
21 | final class HtmlDiff implements Diff {
22 |
23 | protected HtmlDiff() {
24 | }
25 |
26 | @Override
27 | // @formatter:off
28 | public native void diff(String before, String after, ListTarget target) /*-{
29 | var dmp = new $wnd.diff_match_patch();
30 | var diffs = dmp.diff_main(before, after);
31 | dmp.diff_cleanupSemantic(diffs);
32 | var cursor = 0;
33 | for (var i in diffs) {
34 | var text = diffs[i][1], len = text.length;
35 | switch (diffs[i][0]) {
36 | case 0:
37 | cursor += len;
38 | break;
39 | case 1:
40 | target.@com.goodow.realtime.core.Diff.ListTarget::insert(ILjava/lang/Object;)(cursor, text);
41 | cursor += len;
42 | break;
43 | case -1:
44 | target.@com.goodow.realtime.core.Diff.ListTarget::remove(II)(cursor, len);
45 | break;
46 | default:
47 | throw @java.lang.RuntimeException::new(Ljava/lang/String;)("Shouldn't reach here!");
48 | }
49 | }
50 | }-*/;
51 | // @formatter:on
52 |
53 |
54 | @Override
55 | public void diff(JsonArray before, JsonArray after, ListTarget target,
56 | Comparator comparator) {
57 |
58 | }
59 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/VertxPlatform.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.core.Diff;
17 | import com.goodow.realtime.core.Net;
18 | import com.goodow.realtime.core.Platform;
19 | import com.goodow.realtime.core.Platform.Type;
20 | import com.goodow.realtime.core.PlatformFactory;
21 | import com.goodow.realtime.core.Scheduler;
22 | import com.goodow.realtime.core.WebSocket;
23 | import com.goodow.realtime.json.JsonObject;
24 |
25 | import org.vertx.java.core.Vertx;
26 |
27 | public class VertxPlatform implements PlatformFactory {
28 | /**
29 | * Registers the Vertx platform with a default configuration.
30 | */
31 | public static void register(Vertx vertx) {
32 | Platform.setFactory(new VertxPlatform(vertx));
33 | }
34 |
35 | protected final Scheduler scheduler;
36 | protected final Net net;
37 | private final JavaDiff diff;
38 |
39 | private VertxPlatform(final Vertx vertx) {
40 | scheduler = new VertxScheduler(vertx);
41 | net = new Net() {
42 | @Override
43 | public WebSocket createWebSocket(String url, JsonObject options) {
44 | return new VertxWebSocket(vertx, url);
45 | }
46 | };
47 | diff = new JavaDiff();
48 | }
49 |
50 | @Override
51 | public Diff diff() {
52 | return diff;
53 | }
54 |
55 | @Override
56 | public Net net() {
57 | return net;
58 | }
59 |
60 | @Override
61 | public Scheduler scheduler() {
62 | return scheduler;
63 | }
64 |
65 | @Override
66 | public Type type() {
67 | return Type.VERTX;
68 | }
69 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/core/Registrations.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.core;
15 |
16 | import com.goodow.realtime.json.Json;
17 | import com.goodow.realtime.json.JsonArray;
18 |
19 | /**
20 | * A {@link Registration} that will call {@link Registration#unregister()} on
21 | * all added handlers if {@link Registration#unregister()} is called on this object.
22 | */
23 | public class Registrations implements Registration {
24 | private JsonArray registrations;
25 |
26 | public Registrations add(Registration registration) {
27 | assert registration != null : "registration shouldn't be null";
28 | if (registrations == null) {
29 | registrations = Json.createArray();
30 | }
31 | registrations.push(registration);
32 | return this;
33 | }
34 |
35 | @Override
36 | public void unregister() {
37 | if (registrations != null) {
38 | registrations.forEach(new JsonArray.ListIterator() {
39 | @Override
40 | public void call(int index, Registration value) {
41 | value.unregister();
42 | }
43 | });
44 | // make sure we remove the handlers to avoid potential leaks
45 | // if someone fails to null out their reference to us
46 | registrations.clear();
47 | registrations = null;
48 | }
49 | }
50 |
51 | public Registration wrap(final Registration registration) {
52 | add(registration);
53 | return new Registration() {
54 | @Override
55 | public void unregister() {
56 | registrations.removeValue(registration);
57 | registration.unregister();
58 | }
59 | };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/google/gwt/core/client/js/JsType.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 | package com.google.gwt.core.client.js;
17 |
18 | import java.lang.annotation.Documented;
19 | import java.lang.annotation.ElementType;
20 | import java.lang.annotation.Retention;
21 | import java.lang.annotation.RetentionPolicy;
22 | import java.lang.annotation.Target;
23 |
24 | /**
25 | * JsType is used to describe the interface of a Javascript object, either one that already
26 | * exists from the external Javascript environment, or one that will be accessible to the
27 | * external Javascript environment. Calls to methods on interfaces marked with this annotation
28 | * are treated specially by the GWT compiler for interoperability purposes. Such methods need
29 | * not be backed by an Overlay type implementation, the GWT compiler will assume that a JS method on
30 | * the prototype of the underlying reference will match the name of the method on this interface.
31 | *
32 | * Furthermore, if the JsType is marked with a prototype reference, then concrete
33 | * implementations of the class emitted by the GWT compiler will use the specified prototype as
34 | * opposed the the ordinary one (e.g. java.lang.Object).
35 | *
36 | * JsTypes act like JavaScriptObject in terms of castability, except when a prototype is
37 | * specified, in which case, cast checks and instanceof checks will be delegated to the native
38 | * JS instanceof operator.
39 | */
40 | @Retention(RetentionPolicy.RUNTIME)
41 | @Target(ElementType.TYPE)
42 | @Documented
43 | public @interface JsType {
44 | String prototype() default "";
45 | boolean isNative() default false;
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/VertxScheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.core.Handler;
17 | import com.goodow.realtime.core.Scheduler;
18 |
19 | import org.vertx.java.core.Vertx;
20 |
21 | class VertxScheduler implements Scheduler {
22 | private final Vertx vertx;
23 |
24 | VertxScheduler(Vertx vertx) {
25 | this.vertx = vertx;
26 | }
27 |
28 | @Override
29 | public boolean cancelTimer(int id) {
30 | return vertx.cancelTimer(id);
31 | }
32 |
33 | @SuppressWarnings("unchecked")
34 | @Override
35 | public void handle(Object handler, Object event) {
36 | ((Handler) handler).handle(event);
37 | }
38 |
39 | @Override
40 | public void scheduleDeferred(final Handler handler) {
41 | vertx.runOnContext(new org.vertx.java.core.Handler() {
42 | @Override
43 | public void handle(Void event) {
44 | handler.handle(null);
45 | }
46 | });
47 | }
48 |
49 | @Override
50 | public int scheduleDelay(int delayMs, final Handler handler) {
51 | return (int) vertx.setTimer(delayMs, new org.vertx.java.core.Handler() {
52 | @Override
53 | public void handle(Long event) {
54 | handler.handle(null);
55 | }
56 | });
57 | }
58 |
59 | @Override
60 | public int schedulePeriodic(int delayMs, final Handler handler) {
61 | return (int) vertx.setPeriodic(delayMs, new org.vertx.java.core.Handler() {
62 | @Override
63 | public void handle(Long event) {
64 | handler.handle(null);
65 | }
66 | });
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/BusHookProxy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.BusHook;
17 | import com.goodow.realtime.channel.Message;
18 | import com.goodow.realtime.core.Handler;
19 |
20 | public abstract class BusHookProxy implements BusHook {
21 | @Override
22 | public void handleOpened() {
23 | if (delegate() != null) {
24 | delegate().handleOpened();
25 | }
26 | }
27 |
28 | @Override
29 | public void handlePostClose() {
30 | if (delegate() != null) {
31 | delegate().handlePostClose();
32 | }
33 | }
34 |
35 | @Override
36 | public boolean handlePreClose() {
37 | return delegate() == null ? true : delegate().handlePreClose();
38 | }
39 |
40 | @SuppressWarnings("rawtypes")
41 | @Override
42 | public boolean handlePreSubscribe(String topic, Handler extends Message> handler) {
43 | return delegate() == null ? true : delegate().handlePreSubscribe(topic, handler);
44 | }
45 |
46 | @Override
47 | public boolean handleReceiveMessage(Message> message) {
48 | return delegate() == null ? true : delegate().handleReceiveMessage(message);
49 | }
50 |
51 | @Override
52 | public boolean handleSendOrPub(boolean send, String topic, Object msg,
53 | Handler> replyHandler) {
54 | return delegate() == null ? true : delegate().handleSendOrPub(send, topic, msg,
55 | replyHandler);
56 | }
57 |
58 | @Override
59 | public boolean handleUnsubscribe(String topic) {
60 | return delegate() == null ? true : delegate().handleUnsubscribe(topic);
61 | }
62 |
63 | protected abstract BusHook delegate();
64 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/Message.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel;
15 |
16 | import com.google.gwt.core.client.js.JsNoExport;
17 | import com.google.gwt.core.client.js.JsType;
18 |
19 | import com.goodow.realtime.core.Handler;
20 |
21 | /**
22 | * Represents a message on the event bus.
23 | */
24 | @JsType
25 | public interface Message {
26 | /**
27 | * The body of the message
28 | */
29 | T body();
30 |
31 | /**
32 | * Signal that processing of this message failed. If the message was sent specifying a result
33 | * handler the handler will be called with a failure corresponding to the failure code and message
34 | * specified here
35 | *
36 | * @param failureCode A failure code to pass back to the sender
37 | * @param msg A message to pass back to the sender
38 | */
39 | void fail(int failureCode, String msg);
40 |
41 | /**
42 | * @return Whether this message originated in the local session.
43 | */
44 | boolean isLocal();
45 |
46 | /**
47 | * Reply to this message. If the message was sent specifying a reply handler, that handler will be
48 | * called when it has received a reply. If the message wasn't sent specifying a receipt handler
49 | * this method does nothing.
50 | */
51 | @JsNoExport
52 | void reply(Object msg);
53 |
54 | /**
55 | * The same as {@code reply(Object msg)} but you can specify handler for the reply - i.e. to
56 | * receive the reply to the reply.
57 | */
58 | @SuppressWarnings("hiding")
59 | void reply(Object msg, Handler> replyHandler);
60 |
61 | /**
62 | * The reply topic (if any)
63 | */
64 | String replyTopic();
65 |
66 | /**
67 | * The topic the message was sent to
68 | */
69 | String topic();
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/BusHook.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel;
15 |
16 | import com.goodow.realtime.core.Handler;
17 |
18 | /**
19 | * A hook that you can use to receive various events on the Bus.
20 | */
21 | public interface BusHook {
22 | /**
23 | * Called when the bus is opened
24 | */
25 | void handleOpened();
26 |
27 | /**
28 | * Called when the bus is closed
29 | */
30 | void handlePostClose();
31 |
32 | /**
33 | * Called before close the bus
34 | *
35 | * @return true to close the bus, false to reject it
36 | */
37 | boolean handlePreClose();
38 |
39 | /**
40 | * Called before register a handler
41 | *
42 | * @param topic The topic
43 | * @param handler The handler
44 | * @return true to let the registration occur, false otherwise
45 | */
46 | @SuppressWarnings("rawtypes")
47 | boolean handlePreSubscribe(String topic, Handler extends Message> handler);
48 |
49 | /**
50 | * Called when a message is received
51 | *
52 | * @param message The message
53 | * @return true To allow the message to deliver, false otherwise
54 | */
55 | boolean handleReceiveMessage(Message> message);
56 |
57 | /**
58 | * Called when sending or publishing on the bus
59 | *
60 | * @param send if true it's a send else it's a publish
61 | * @param topic The topic the message is being sent/published to
62 | * @param msg The message
63 | * @param replyHandler Reply handler will be called when any reply from the recipient is received
64 | * @return true To allow the send/publish to occur, false otherwise
65 | */
66 | boolean handleSendOrPub(boolean send, String topic, Object msg,
67 | Handler> replyHandler);
68 |
69 | /**
70 | * Called when unregistering a handler
71 | *
72 | * @param topic The topic
73 | * @return true to let the unregistration occur, false otherwise
74 | */
75 | boolean handleUnsubscribe(String topic);
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/html/SockJS.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.html;
15 |
16 | import com.goodow.realtime.channel.State;
17 | import com.goodow.realtime.core.WebSocket;
18 | import com.goodow.realtime.json.JsonObject;
19 |
20 | import com.google.gwt.core.client.JavaScriptObject;
21 |
22 | /**
23 | * SockJS implementation of {@link WebSocket}.
24 | */
25 | final class SockJS extends JavaScriptObject implements WebSocket {
26 |
27 | // @formatter:off
28 | public static native WebSocket create(String url, Object _reserved, JsonObject options) /*-{
29 | return new $wnd.SockJS(url, _reserved, options);
30 | }-*/;
31 |
32 | protected SockJS() {
33 | }
34 |
35 | @Override
36 | public native void close() /*-{
37 | this.close();
38 | }-*/;
39 |
40 | @Override
41 | public State getReadyState() {
42 | return State.values()[nativeGetReadyState()];
43 | }
44 |
45 | @Override
46 | public native void send(String data) /*-{
47 | this.send(data);
48 | }-*/;
49 |
50 | @Override
51 | public native void setListen(WebSocketHandler handler) /*-{
52 | if (!handler) {
53 | this.onopen = null;
54 | this.onclose = null;
55 | this.onmessage = null;
56 | this.onerror = null;
57 | return;
58 | }
59 |
60 | this.onopen = function(e) {
61 | handler.@com.goodow.realtime.core.WebSocket.WebSocketHandler::onOpen()();
62 | };
63 | this.onclose = function(e) {
64 | handler.@com.goodow.realtime.core.WebSocket.WebSocketHandler::onClose(Lcom/goodow/realtime/json/JsonObject;)(e);
65 | };
66 | this.onmessage = function(e) {
67 | handler.@com.goodow.realtime.core.WebSocket.WebSocketHandler::onMessage(Ljava/lang/String;)(e.data);
68 | };
69 | this.onerror = function(e) {
70 | handler.@com.goodow.realtime.core.WebSocket.WebSocketHandler::onError(Ljava/lang/String;)(e);
71 | };
72 | }-*/;
73 |
74 | private native int nativeGetReadyState() /*-{
75 | return this.readyState;
76 | }-*/;
77 | // @formatter:on
78 |
79 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/MessageImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.Message;
18 | import com.goodow.realtime.core.Handler;
19 |
20 | class MessageImpl implements Message {
21 | protected U body;
22 | protected Bus bus;
23 | protected String topic;
24 | protected String replyTopic;
25 | protected boolean send; // Is it a send or a publish?
26 | protected boolean local;
27 |
28 | public MessageImpl(boolean local, boolean send, Bus bus, String topic, String replyTopic,
29 | U body) {
30 | this.local = local;
31 | this.send = send;
32 | this.bus = bus;
33 | this.topic = topic;
34 | this.replyTopic = replyTopic;
35 | this.body = body;
36 | }
37 |
38 | @Override
39 | public String topic() {
40 | return topic;
41 | }
42 |
43 | @Override
44 | public U body() {
45 | return body;
46 | }
47 |
48 | @Override
49 | public void fail(int failureCode, String msg) {
50 | // sendReply(new ReplyException(ReplyFailure.RECIPIENT_FAILURE, failureCode, message), null);
51 | }
52 |
53 | @Override
54 | public boolean isLocal() {
55 | return local;
56 | }
57 |
58 | @Override
59 | public void reply(Object msg) {
60 | sendReply(msg, null);
61 | }
62 |
63 | @Override
64 | public void reply(Object msg, Handler> replyHandler) {
65 | sendReply(msg, replyHandler);
66 | }
67 |
68 | @Override
69 | public String replyTopic() {
70 | return replyTopic;
71 | }
72 |
73 | @Override
74 | public String toString() {
75 | return body == null ? null : body.toString();
76 | }
77 |
78 | private void sendReply(Object msg, Handler> replyHandler) {
79 | if (bus != null && replyTopic != null) {
80 | // Send back reply
81 | if (local) {
82 | bus.sendLocal(replyTopic, msg, replyHandler);
83 | } else {
84 | bus.send(replyTopic, msg, replyHandler);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/BusProxy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.BusHook;
18 | import com.goodow.realtime.channel.Message;
19 | import com.goodow.realtime.channel.State;
20 | import com.goodow.realtime.core.Handler;
21 | import com.goodow.realtime.core.Registration;
22 |
23 | public abstract class BusProxy implements Bus {
24 | protected final Bus delegate;
25 | protected BusHook hook;
26 |
27 | public BusProxy(Bus delegate) {
28 | this.delegate = delegate;
29 | }
30 |
31 | @Override
32 | public void close() {
33 | delegate.close();
34 | }
35 |
36 | public Bus getDelegate() {
37 | return delegate;
38 | }
39 |
40 | @Override
41 | public State getReadyState() {
42 | return delegate.getReadyState();
43 | }
44 |
45 | @Override
46 | public String getSessionId() {
47 | return delegate.getSessionId();
48 | }
49 |
50 | @Override
51 | public Bus publish(String topic, Object msg) {
52 | return delegate.publish(topic, msg);
53 | }
54 |
55 | @Override
56 | public Bus publishLocal(String topic, Object msg) {
57 | return delegate.publishLocal(topic, msg);
58 | }
59 |
60 | @SuppressWarnings("rawtypes")
61 | @Override
62 | public Registration subscribe(String topic, Handler extends Message> handler) {
63 | return delegate.subscribe(topic, handler);
64 | }
65 |
66 | @SuppressWarnings("rawtypes")
67 | @Override
68 | public Registration subscribeLocal(String topic, Handler extends Message> handler) {
69 | return delegate.subscribeLocal(topic, handler);
70 | }
71 |
72 | @Override
73 | public Bus send(String topic, Object msg, Handler> replyHandler) {
74 | return delegate.send(topic, msg, replyHandler);
75 | }
76 |
77 | @Override
78 | public Bus sendLocal(String topic, Object msg, Handler> replyHandler) {
79 | return delegate.sendLocal(topic, msg, replyHandler);
80 | }
81 |
82 | @Override
83 | public Bus setHook(BusHook hook) {
84 | this.hook = hook;
85 | return this;
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/ChannelBridge.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server;
15 |
16 | import org.vertx.java.core.AsyncResult;
17 | import org.vertx.java.core.AsyncResultHandler;
18 | import org.vertx.java.core.Vertx;
19 | import org.vertx.java.core.http.HttpServer;
20 | import org.vertx.java.core.impl.CountingCompletionHandler;
21 | import org.vertx.java.core.json.JsonArray;
22 | import org.vertx.java.core.json.JsonObject;
23 | import org.vertx.java.core.sockjs.EventBusBridgeHook;
24 | import org.vertx.java.core.sockjs.SockJSServer;
25 |
26 | public class ChannelBridge {
27 | private final Vertx vertx;
28 | private final JsonObject config;
29 | private EventBusBridgeHook hook;
30 |
31 | public ChannelBridge(Vertx vertx, JsonObject config) {
32 | this.vertx = vertx;
33 | this.config = config;
34 | }
35 |
36 | public void bridge(final CountingCompletionHandler countDownLatch) {
37 | HttpServer server = vertx.createHttpServer();
38 | SockJSServer sjsServer = vertx.createSockJSServer(server).setHook(hook);
39 | JsonObject empty = new JsonObject();
40 | JsonArray all = new JsonArray().add(empty);
41 | JsonArray inboundPermitted = config.getArray("inbound_permitted", all);
42 | JsonArray outboundPermitted = config.getArray("outbound_permitted", all);
43 |
44 | sjsServer.bridge(config.getObject("sjs_config", new JsonObject()
45 | .putString("prefix", "/channel")), inboundPermitted, outboundPermitted, config.getObject(
46 | "bridge_config", empty));
47 |
48 | countDownLatch.incRequired();
49 | server.listen(config.getInteger("port", 1986), config.getString("host", "0.0.0.0"),
50 | new AsyncResultHandler() {
51 | @Override
52 | public void handle(AsyncResult ar) {
53 | if (!ar.succeeded()) {
54 | countDownLatch.failed(ar.cause());
55 | } else {
56 | countDownLatch.complete();
57 | }
58 | }
59 | });
60 | }
61 |
62 | public ChannelBridge setHook(EventBusBridgeHook hook) {
63 | this.hook = hook;
64 | return this;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/VertxMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.channel.BusHook;
17 | import com.goodow.realtime.channel.Message;
18 | import com.goodow.realtime.core.Handler;
19 |
20 | class VertxMessage implements Message {
21 | private final VertxBus bus;
22 | private final org.vertx.java.core.eventbus.Message delegate;
23 |
24 | public VertxMessage(VertxBus bus, org.vertx.java.core.eventbus.Message delegate) {
25 | this.bus = bus;
26 | this.delegate = delegate;
27 | }
28 |
29 | @Override
30 | public String topic() {
31 | return delegate.address();
32 | }
33 |
34 | @SuppressWarnings("unchecked")
35 | @Override
36 | public T body() {
37 | return (T) VertxBus.unwrapMsg(delegate.body());
38 | }
39 |
40 | @Override
41 | public void fail(int failureCode, String msg) {
42 | delegate.fail(failureCode, msg);
43 | }
44 |
45 | @Override
46 | public boolean isLocal() {
47 | return false;
48 | }
49 |
50 | @Override
51 | public void reply(Object msg) {
52 | reply(msg, null);
53 | }
54 |
55 | @SuppressWarnings("hiding")
56 | @Override
57 | public void reply(Object msg, final Handler> replyHandler) {
58 | BusHook hook = bus.getHook();
59 | if (hook == null || hook.handleSendOrPub(true, replyTopic(), msg, replyHandler)) {
60 | org.vertx.java.core.Handler> handler =
61 | replyHandler == null ? null
62 | : new org.vertx.java.core.Handler>() {
63 | @Override
64 | public void handle(org.vertx.java.core.eventbus.Message message) {
65 | VertxMessage event = new VertxMessage(bus, message);
66 | BusHook hook = bus.getHook();
67 | if (hook == null || hook.handleReceiveMessage(event)) {
68 | replyHandler.handle(event);
69 | }
70 | }
71 | };
72 | delegate.reply(VertxBus.wrapMsg(msg), handler);
73 | }
74 | }
75 |
76 | @Override
77 | public String replyTopic() {
78 | return delegate.replyAddress();
79 | }
80 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/html/HtmlScheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.html;
15 |
16 | import com.goodow.realtime.core.Handler;
17 | import com.goodow.realtime.core.Scheduler;
18 | import com.goodow.realtime.json.Json;
19 | import com.goodow.realtime.json.JsonObject;
20 |
21 | import com.google.gwt.core.client.Scheduler.RepeatingCommand;
22 | import com.google.gwt.core.client.Scheduler.ScheduledCommand;
23 |
24 | class HtmlScheduler implements Scheduler {
25 | // @formatter:off
26 | private static native void nativeHandle(Object handler, T event) /*-{
27 | handler(event);
28 | }-*/;
29 | // @formatter:on
30 |
31 | private int timerId = -1;
32 | private final JsonObject timers = Json.createObject();
33 |
34 | @Override
35 | public boolean cancelTimer(int id) {
36 | String key = "" + id;
37 | if (timers.has(key)) {
38 | timers.remove(key);
39 | return true;
40 | }
41 | return false;
42 | }
43 |
44 | @SuppressWarnings("unchecked")
45 | @Override
46 | public void handle(Object handler, Object event) {
47 | if (handler instanceof Handler) {
48 | ((Handler) handler).handle(event);
49 | } else {
50 | nativeHandle(handler, event);
51 | }
52 | }
53 |
54 | @Override
55 | public void scheduleDeferred(final Handler handler) {
56 | com.google.gwt.core.client.Scheduler.get().scheduleDeferred(new ScheduledCommand() {
57 | @Override
58 | public void execute() {
59 | handler.handle(null);
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | public int scheduleDelay(int delayMs, final Handler handler) {
66 | final String key = "" + (++timerId);
67 | RepeatingCommand cmd = new RepeatingCommand() {
68 | @Override
69 | public boolean execute() {
70 | if (timers.has(key)) {
71 | timers.remove(key);
72 | handler.handle(null);
73 | }
74 | return false;
75 | }
76 | };
77 | timers.set(key, cmd);
78 | com.google.gwt.core.client.Scheduler.get().scheduleFixedDelay(cmd, delayMs);
79 | return timerId;
80 | }
81 |
82 | @Override
83 | public int schedulePeriodic(int delayMs, final Handler handler) {
84 | final String key = "" + (++timerId);
85 | RepeatingCommand cmd = new RepeatingCommand() {
86 | @Override
87 | public boolean execute() {
88 | if (timers.has(key)) {
89 | handler.handle(null);
90 | return true;
91 | }
92 | return false;
93 | }
94 | };
95 | timers.set(key, cmd);
96 | com.google.gwt.core.client.Scheduler.get().scheduleFixedPeriod(cmd, delayMs);
97 | return timerId;
98 | }
99 | }
--------------------------------------------------------------------------------
/src/test/java/com/goodow/realtime/channel/server/VertxBusTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.Message;
18 | import com.goodow.realtime.channel.MessageHandler;
19 | import com.goodow.realtime.channel.server.impl.VertxBus;
20 | import com.goodow.realtime.core.Handler;
21 | import com.goodow.realtime.json.Json;
22 | import com.goodow.realtime.json.JsonObject;
23 |
24 | import static org.vertx.testtools.VertxAssert.assertNotNull;
25 | import static org.vertx.testtools.VertxAssert.assertTrue;
26 |
27 | import org.junit.Test;
28 | import org.vertx.java.core.AsyncResult;
29 | import org.vertx.java.core.AsyncResultHandler;
30 | import org.vertx.testtools.TestVerticle;
31 | import org.vertx.testtools.VertxAssert;
32 |
33 | public class VertxBusTest extends TestVerticle {
34 |
35 | private Bus bus;
36 |
37 | @Override
38 | public void start() {
39 | initialize();
40 |
41 | // Deploy the module - the System property `vertx.modulename` will contain the name of the
42 | // module so you don't have to hardecode it in your tests
43 | container.deployModule(System.getProperty("vertx.modulename"),
44 | new AsyncResultHandler() {
45 | @Override
46 | public void handle(AsyncResult asyncResult) {
47 | assertTrue(asyncResult.succeeded());
48 | assertNotNull("deploymentID should not be null", asyncResult.result());
49 |
50 | bus = new VertxBus(vertx.eventBus());
51 | startTests();
52 | }
53 | });
54 | }
55 |
56 | @Test
57 | public void test() {
58 | bus.subscribe("somea/topic", new MessageHandler() {
59 | @Override
60 | public void handle(Message message) {
61 | VertxAssert.assertEquals("send1", message.body().getString("text"));
62 |
63 | JsonObject o1 = Json.createObject().set("text", "reply1");
64 | message.reply(o1, new Handler>() {
65 | @Override
66 | public void handle(Message message) {
67 | VertxAssert.assertEquals("reply2", message.body().getString("text"));
68 | VertxAssert.assertNull(message.replyTopic());
69 |
70 | bus.close();
71 | VertxAssert.testComplete();
72 | }
73 | });
74 | }
75 | });
76 |
77 | JsonObject o1 = Json.createObject().set("text", "send1");
78 | bus.send("somea/topic", o1, new Handler>() {
79 | @Override
80 | public void handle(Message message) {
81 | VertxAssert.assertEquals("reply1", message.body().getString("text"));
82 |
83 | JsonObject o1 = Json.createObject().set("text", "reply2");
84 | message.reply(o1, null);
85 | }
86 | });
87 | }
88 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/util/FuzzingBackOffGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.util;
15 |
16 | /**
17 | * Simple fibonacci back off with fuzzing. This is important for reconnection so that everyone
18 | * doesn't retry at the same time.
19 | */
20 | public class FuzzingBackOffGenerator {
21 | public static class BackOffParameters {
22 | public final int targetDelay;
23 | public final int minimumDelay;
24 |
25 | private BackOffParameters(int targetDelay, int minimumDelay) {
26 | this.targetDelay = targetDelay;
27 | this.minimumDelay = minimumDelay;
28 | }
29 | }
30 |
31 | /** Randomization factor. Must be between 0 and 1. */
32 | private final double randomizationFactor;
33 | /** The first time we back off. */
34 | private final int initialBackOff;
35 | /** The max back off value, it'll be fuzzed. */
36 | private final int maxBackOff;
37 | /** The next time we've backed off. */
38 | private int nextBackOffTime;
39 | /** The current back off time. */
40 | private int backOffTime;
41 |
42 | /**
43 | * @param initialBackOff Initial value to back off. This class does not interpret the meaning of
44 | * this value. must be > 0
45 | * @param maxBackOff Max value to back off
46 | * @param randomizationFactor between 0 and 1 to control the range of randomness.
47 | */
48 | public FuzzingBackOffGenerator(int initialBackOff, int maxBackOff, double randomizationFactor) {
49 | if (randomizationFactor < 0 || randomizationFactor > 1) {
50 | throw new IllegalArgumentException("randomizationFactor must be between 0 and 1. actual "
51 | + randomizationFactor);
52 | }
53 |
54 | if (initialBackOff <= 0) {
55 | throw new IllegalArgumentException("initialBackOff must be between 0 and 1. actual "
56 | + initialBackOff);
57 | }
58 |
59 | this.randomizationFactor = randomizationFactor;
60 | this.initialBackOff = initialBackOff;
61 | this.maxBackOff = maxBackOff;
62 | this.nextBackOffTime = initialBackOff;
63 | this.backOffTime = 0;
64 | }
65 |
66 | /** Gets the next back off time. Until maxBackOff is reached. */
67 | public BackOffParameters next() {
68 | int ret = Math.min(nextBackOffTime, maxBackOff);
69 | nextBackOffTime += backOffTime;
70 | if (nextBackOffTime <= 0) {
71 | nextBackOffTime = Integer.MAX_VALUE;
72 | }
73 | backOffTime = ret;
74 |
75 | int randomizeTime = (int) (backOffTime * (1.0 + (Math.random() * randomizationFactor)));
76 | int minAllowedTime = (int) Math.round(randomizeTime - backOffTime * randomizationFactor);
77 |
78 | return new BackOffParameters(randomizeTime, minAllowedTime);
79 | }
80 |
81 | /**
82 | * Resets the back off.
83 | */
84 | public void reset() {
85 | nextBackOffTime = initialBackOff;
86 | backOffTime = 0;
87 | }
88 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/objc/ObjCScheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.objc;
15 |
16 | import com.goodow.realtime.core.Handler;
17 | import com.goodow.realtime.core.Scheduler;
18 | import com.goodow.realtime.json.Json;
19 | import com.goodow.realtime.json.JsonObject;
20 |
21 | import java.util.concurrent.atomic.AtomicInteger;
22 |
23 | /*-[
24 | #import "GDChannel.h"
25 | ]-*/
26 | class ObjCScheduler implements Scheduler {
27 | // @formatter:off
28 | private static native void nativeCancelTimer(Object timer) /*-[
29 | [(NSTimer *)timer invalidate];
30 | ]-*/;
31 |
32 | private static native void nativeHandle(Object handler, T event) /*-[
33 | GDCMessageHandler block = (GDCMessageHandler)handler;
34 | block(event);
35 | ]-*/;
36 |
37 | private static native Object nativeScheduleTimer(int delayMs, boolean repeat,
38 | Handler handler) /*-[
39 | return
40 | [NSTimer scheduledTimerWithTimeInterval:delayMs/1000
41 | target:handler
42 | selector:@selector(handleWithId:)
43 | userInfo:nil
44 | repeats:repeat];
45 | ]-*/;
46 | // @formatter:on
47 |
48 | private final AtomicInteger timerId = new AtomicInteger(0);
49 | private final JsonObject timers = Json.createObject();
50 |
51 | @Override
52 | public boolean cancelTimer(int id) {
53 | String key = "" + id;
54 | if (timers.has(key)) {
55 | nativeCancelTimer(timers.get(key));
56 | timers.remove(key);
57 | return true;
58 | }
59 | return false;
60 | }
61 |
62 | @SuppressWarnings("unchecked")
63 | @Override
64 | public void handle(Object handler, Object event) {
65 | if (handler instanceof Handler) {
66 | ((Handler) handler).handle(event);
67 | } else {
68 | nativeHandle(handler, event);
69 | }
70 | }
71 |
72 | @Override
73 | // @formatter:off
74 | public native void scheduleDeferred(Handler handler) /*-[
75 | [[NSRunLoop mainRunLoop] performSelector:@selector(handleWithId:)
76 | target:handler
77 | argument:nil
78 | order:0
79 | modes:@[NSDefaultRunLoopMode]];
80 | ]-*/;
81 | // @formatter:on
82 |
83 | @Override
84 | public int scheduleDelay(int delayMs, final Handler handler) {
85 | final int id = timerId.getAndIncrement();
86 | timers.set("" + id, nativeScheduleTimer(delayMs, false, handler));
87 | return id;
88 | }
89 |
90 | @Override
91 | public int schedulePeriodic(int delayMs, Handler handler) {
92 | final int id = timerId.getAndIncrement();
93 | timers.set("" + id, nativeScheduleTimer(delayMs, true, handler));
94 | return id;
95 | }
96 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/core/impl/FutureResultImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.core.impl;
15 |
16 | import com.goodow.realtime.core.AsyncResult;
17 | import com.goodow.realtime.core.Future;
18 | import com.goodow.realtime.core.Handler;
19 |
20 | public class FutureResultImpl implements Future {
21 | private boolean failed;
22 | private boolean succeeded;
23 | private Handler> handler;
24 | private T result;
25 | private Throwable throwable;
26 |
27 | /**
28 | * Create a AsyncResult that hasn't completed yet
29 | */
30 | public FutureResultImpl() {
31 | }
32 |
33 | /**
34 | * Create a AsyncResult that has already succeeded
35 | *
36 | * @param result The result
37 | */
38 | public FutureResultImpl(T result) {
39 | setResult(result);
40 | }
41 |
42 | /**
43 | * Create a AsyncResult that has already completed
44 | *
45 | * @param t The Throwable or null if succeeded
46 | */
47 | public FutureResultImpl(Throwable t) {
48 | if (t == null) {
49 | setResult(null);
50 | } else {
51 | setFailure(t);
52 | }
53 | }
54 |
55 | /**
56 | * An exception describing failure. This will be null if the operation succeeded.
57 | */
58 | @Override
59 | public Throwable cause() {
60 | return throwable;
61 | }
62 |
63 | /**
64 | * Has it completed?
65 | */
66 | @Override
67 | public boolean complete() {
68 | return failed || succeeded;
69 | }
70 |
71 | /**
72 | * Did it fail?
73 | */
74 | @Override
75 | public boolean failed() {
76 | return failed;
77 | }
78 |
79 | /**
80 | * The result of the operation. This will be null if the operation failed.
81 | */
82 | @Override
83 | public T result() {
84 | return result;
85 | }
86 |
87 | /**
88 | * Set the failure. Any handler will be called, if there is one
89 | */
90 | @Override
91 | public FutureResultImpl setFailure(Throwable throwable) {
92 | this.throwable = throwable;
93 | failed = true;
94 | checkCallHandler();
95 | return this;
96 | }
97 |
98 | /**
99 | * Set a handler for the result. It will get called when it's complete
100 | */
101 | @Override
102 | public FutureResultImpl setHandler(Handler> handler) {
103 | this.handler = handler;
104 | checkCallHandler();
105 | return this;
106 | }
107 |
108 | /**
109 | * Set the result. Any handler will be called, if there is one
110 | */
111 | @Override
112 | public FutureResultImpl setResult(T result) {
113 | this.result = result;
114 | succeeded = true;
115 | checkCallHandler();
116 | return this;
117 | }
118 |
119 | /**
120 | * Did it succeeed?
121 | */
122 | public boolean succeeded() {
123 | return succeeded;
124 | }
125 |
126 | private void checkCallHandler() {
127 | if (handler != null && complete()) {
128 | handler.handle(this);
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/BridgeHook.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.channel.impl.WebSocketBus;
17 |
18 | import org.vertx.java.core.AsyncResult;
19 | import org.vertx.java.core.Handler;
20 | import org.vertx.java.core.Vertx;
21 | import org.vertx.java.core.eventbus.EventBus;
22 | import org.vertx.java.core.json.JsonObject;
23 | import org.vertx.java.core.shareddata.SharedData;
24 | import org.vertx.java.core.sockjs.EventBusBridgeHook;
25 | import org.vertx.java.core.sockjs.SockJSSocket;
26 |
27 | import java.util.HashMap;
28 | import java.util.Map;
29 | import java.util.Set;
30 |
31 | public class BridgeHook implements EventBusBridgeHook {
32 | public static final String TOPIC = "topic";
33 | public static final String SESSION_WATCH_ADDR =
34 | WebSocketBus.TOPIC_CHANNEL + "/" + WebSocketBus.SESSION + "/_watch";
35 | private final EventBus eb;
36 | private final Map connections = new HashMap();
37 | private final SharedData sharedData;
38 |
39 | public static String getSessionsKey(String topic) {
40 | return WebSocketBus.TOPIC_CHANNEL + "/" + topic + "/" + WebSocketBus.SESSION;
41 | }
42 |
43 | public BridgeHook(Vertx vertx) {
44 | this.eb = vertx.eventBus();
45 | this.sharedData = vertx.sharedData();
46 | }
47 |
48 | @Override
49 | public void handlePostRegister(SockJSSocket sock, String topic) {
50 | Set sessionIds = sharedData.getSet(getSessionsKey(topic));
51 | String sessionId = connections.get(sock.writeHandlerID());
52 | sessionIds.add(sessionId);
53 | publishPresence(topic, sessionId, true);
54 | }
55 |
56 | @Override
57 | public boolean handlePreRegister(SockJSSocket sock, String topic) {
58 | return true;
59 | }
60 |
61 | @Override
62 | public boolean handleSendOrPub(SockJSSocket sock, boolean send, JsonObject msg,
63 | final String topic) {
64 | if (WebSocketBus.TOPIC_CONNECT.equals(topic)) {
65 | connections.put(sock.writeHandlerID(), msg.getObject("body").getString(WebSocketBus.SESSION));
66 | } else if (msg.getValue("body") instanceof JsonObject) {
67 | JsonObject body = msg.getObject("body");
68 | if (!body.containsField(WebSocketBus.SESSION)) {
69 | body.putString(WebSocketBus.SESSION, connections.get(sock.writeHandlerID()));
70 | }
71 | }
72 | return true;
73 | }
74 |
75 | @Override
76 | public void handleSocketClosed(SockJSSocket sock) {
77 | connections.remove(sock.writeHandlerID());
78 | }
79 |
80 | @Override
81 | public boolean handleSocketCreated(SockJSSocket sock) {
82 | return true;
83 | }
84 |
85 | @Override
86 | public boolean handleUnregister(SockJSSocket sock, String topic) {
87 | Set sessionIds = sharedData.getSet(getSessionsKey(topic));
88 | String sessionId = connections.get(sock.writeHandlerID());
89 | sessionIds.remove(sessionId);
90 | publishPresence(topic, sessionId, false);
91 | return true;
92 | }
93 |
94 | @Override
95 | public boolean handleAuthorise(JsonObject message, String sessionID,
96 | Handler> handler) {
97 | return false;
98 | }
99 |
100 | private void publishPresence(final String topic, String sessionId, final boolean isJoined) {
101 | JsonObject msg = new JsonObject().putString(WebSocketBus.SESSION, sessionId).putBoolean(
102 | "isJoined", isJoined).putString(TOPIC, topic);
103 | eb.publish(SESSION_WATCH_ADDR, msg);
104 | }
105 | }
--------------------------------------------------------------------------------
/src/test/java/com/goodow/realtime/channel/WebSocketBusTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel;
15 |
16 | import com.goodow.realtime.channel.impl.ReconnectBus;
17 | import com.goodow.realtime.channel.server.impl.VertxPlatform;
18 | import com.goodow.realtime.core.Handler;
19 | import com.goodow.realtime.json.Json;
20 | import com.goodow.realtime.json.JsonObject;
21 |
22 | import static org.vertx.testtools.VertxAssert.assertNotNull;
23 | import static org.vertx.testtools.VertxAssert.assertTrue;
24 |
25 | import org.junit.Test;
26 | import org.vertx.java.core.AsyncResult;
27 | import org.vertx.java.core.AsyncResultHandler;
28 | import org.vertx.testtools.TestVerticle;
29 | import org.vertx.testtools.VertxAssert;
30 |
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 |
34 | public class WebSocketBusTest extends TestVerticle {
35 |
36 | private static final Logger log = Logger.getLogger(WebSocketBusTest.class.getName());
37 | private Bus bus;
38 |
39 | @Override
40 | public void start() {
41 | initialize();
42 | VertxPlatform.register(vertx);
43 |
44 | // Deploy the module - the System property `vertx.modulename` will contain the name of the
45 | // module so you don't have to hardecode it in your tests
46 | container.deployModule(System.getProperty("vertx.modulename"),
47 | new AsyncResultHandler() {
48 | @Override
49 | public void handle(AsyncResult asyncResult) {
50 | assertTrue(asyncResult.succeeded());
51 | assertNotNull("deploymentID should not be null", asyncResult.result());
52 |
53 | bus = new ReconnectBus("ws://localhost:1986/channel/websocket", null);
54 | startTests();
55 | }
56 | });
57 | }
58 |
59 | @Test
60 | public void test() {
61 | bus.subscribeLocal(Bus.ON_OPEN, new MessageHandler() {
62 | @Override
63 | public void handle(Message message) {
64 | log.info("EventBus opened");
65 | }
66 | });
67 | bus.subscribeLocal(Bus.ON_CLOSE, new MessageHandler() {
68 | @Override
69 | public void handle(Message message) {
70 | log.info("EventBus closed");
71 | }
72 | });
73 | bus.subscribeLocal(Bus.ON_ERROR, new MessageHandler() {
74 | @Override
75 | public void handle(Message message) {
76 | log.log(Level.SEVERE, "EventBus Error");
77 | }
78 | });
79 |
80 | JsonObject o1 = Json.createObject().set("text", "send1");
81 | bus.send("some/topic", o1, new Handler>() {
82 | @Override
83 | public void handle(Message message) {
84 | VertxAssert.assertEquals("reply1", message.body().getString("text"));
85 |
86 | JsonObject o1 = Json.createObject().set("text", "reply2");
87 | message.reply(o1, null);
88 | }
89 | });
90 |
91 | bus.subscribe("some/topic", new MessageHandler() {
92 | @Override
93 | public void handle(Message message) {
94 | VertxAssert.assertEquals("send1", message.body().getString("text"));
95 |
96 | JsonObject o1 = Json.createObject().set("text", "reply1");
97 | message.reply(o1, new Handler>() {
98 | @Override
99 | public void handle(Message message) {
100 | VertxAssert.assertEquals("reply2", message.body().getString("text"));
101 | VertxAssert.assertNull(message.replyTopic());
102 |
103 | bus.close();
104 | VertxAssert.testComplete();
105 | }
106 | });
107 | }
108 | });
109 | }
110 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/VertxWebSocket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.channel.State;
17 | import com.goodow.realtime.core.WebSocket;
18 | import com.goodow.realtime.json.Json;
19 |
20 | import org.vertx.java.core.Handler;
21 | import org.vertx.java.core.Vertx;
22 | import org.vertx.java.core.buffer.Buffer;
23 | import org.vertx.java.core.http.HttpClient;
24 |
25 | import java.net.URI;
26 | import java.net.URISyntaxException;
27 | import java.util.logging.Level;
28 | import java.util.logging.Logger;
29 |
30 | class VertxWebSocket implements WebSocket {
31 | private static final Logger log = Logger.getLogger(VertxWebSocket.class.getName());
32 |
33 | private org.vertx.java.core.http.WebSocket socket;
34 | private WebSocketHandler eventHandler;
35 | private State state;
36 |
37 | VertxWebSocket(Vertx vertx, String uri) {
38 | state = State.CONNECTING;
39 | URI serverUri = null;
40 | try {
41 | serverUri = new URI(uri);
42 | } catch (URISyntaxException e) {
43 | throw new RuntimeException(e);
44 | }
45 |
46 | HttpClient client =
47 | vertx.createHttpClient().setHost(serverUri.getHost()).setPort(serverUri.getPort());
48 |
49 | client.connectWebsocket(serverUri.getPath(), new Handler() {
50 | @Override
51 | public void handle(org.vertx.java.core.http.WebSocket ws) {
52 | state = State.OPEN;
53 | socket = ws;
54 | log.info("Websocket Connected");
55 |
56 | socket.closeHandler(new Handler() {
57 | @Override
58 | public void handle(Void event) {
59 | log.info("WebSocket closed");
60 | state = State.CLOSED;
61 | if (eventHandler == null) {
62 | return;
63 | }
64 | eventHandler.onClose(Json.createObject());
65 | socket = null;
66 | }
67 | });
68 |
69 | socket.dataHandler(new Handler() {
70 | @Override
71 | public void handle(Buffer buffer) {
72 | log.finest("Websocket received: " + buffer);
73 | if (eventHandler == null) {
74 | return;
75 | }
76 | eventHandler.onMessage(buffer.toString());
77 | }
78 | });
79 |
80 | socket.exceptionHandler(new Handler() {
81 | @Override
82 | public void handle(Throwable e) {
83 | log.log(Level.SEVERE, "Websocket Failed With Exception", e);
84 | state = State.CLOSING;
85 | if (eventHandler == null) {
86 | return;
87 | }
88 | String message = e.getMessage();
89 | eventHandler.onError(message == null ? e.getClass().getSimpleName() : message);
90 | }
91 | });
92 |
93 | if (eventHandler != null) {
94 | eventHandler.onOpen();
95 | }
96 | }
97 | });
98 | }
99 |
100 | @Override
101 | public void close() {
102 | state = State.CLOSING;
103 | if (socket != null) {
104 | socket.close();
105 | }
106 | }
107 |
108 | @Override
109 | public State getReadyState() {
110 | return state;
111 | }
112 |
113 | @Override
114 | public void send(String data) {
115 | if (socket == null) {
116 | log.warning("WebSocket is closed");
117 | return;
118 | }
119 | try {
120 | log.finest("Websocket send: " + data);
121 | socket.writeTextFrame(data);
122 | } catch (Throwable e) {
123 | throw new RuntimeException(e);
124 | }
125 | }
126 |
127 | @Override
128 | public void setListen(WebSocketHandler handler) {
129 | this.eventHandler = handler;
130 | }
131 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/ReconnectBus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.google.gwt.core.client.js.JsExport;
17 | import com.google.gwt.core.client.js.JsNamespace;
18 |
19 | import com.goodow.realtime.channel.Bus;
20 | import com.goodow.realtime.channel.BusHook;
21 | import com.goodow.realtime.channel.State;
22 | import com.goodow.realtime.channel.util.FuzzingBackOffGenerator;
23 | import com.goodow.realtime.core.Handler;
24 | import com.goodow.realtime.core.Platform;
25 | import com.goodow.realtime.json.Json;
26 | import com.goodow.realtime.json.JsonArray;
27 | import com.goodow.realtime.json.JsonObject;
28 |
29 | @JsNamespace("$wnd.realtime.channel")
30 | @JsExport
31 | public class ReconnectBus extends WebSocketBus {
32 | public static final String AUTO_RECONNECT = "reconnect";
33 | private final FuzzingBackOffGenerator backOffGenerator;
34 | private BusHook hook;
35 | private boolean reconnect;
36 | private final JsonArray queuedMessages = Json.createArray(); // ArrayList()
37 | private final JsonObject options;
38 |
39 | @JsExport
40 | public ReconnectBus(String serverUri, JsonObject options) {
41 | super(serverUri, options);
42 | this.options = options;
43 | reconnect =
44 | options == null || !options.has(AUTO_RECONNECT) ? true : options.getBoolean(AUTO_RECONNECT);
45 | backOffGenerator = new FuzzingBackOffGenerator(1 * 1000, 30 * 60 * 1000, 0.5);
46 |
47 | super.setHook(new BusHookProxy() {
48 | @Override
49 | public void handleOpened() {
50 | backOffGenerator.reset();
51 |
52 | handlerCount.keys().forEach(new JsonArray.ListIterator() {
53 | @Override
54 | public void call(int index, String topic) {
55 | assert handlerCount.getNumber(topic) > 0 : "Handlers registried on " + topic
56 | + " shouldn't be empty";
57 | sendUnsubscribe(topic);
58 | sendSubscribe(topic);
59 | }
60 | });
61 |
62 | if (queuedMessages.length() > 0) {
63 | JsonArray copy = queuedMessages.copy();
64 | queuedMessages.clear();
65 | // Drain any messages that came in while the channel was not open.
66 | copy.forEach(new JsonArray.ListIterator() {
67 | @Override
68 | public void call(int index, JsonObject msg) {
69 | send(msg);
70 | }
71 | });
72 | }
73 | super.handleOpened();
74 | }
75 |
76 | @Override
77 | public void handlePostClose() {
78 | if (reconnect) {
79 | Platform.scheduler().scheduleDelay(backOffGenerator.next().targetDelay,
80 | new Handler() {
81 | @Override
82 | public void handle(Void event) {
83 | if (reconnect) {
84 | reconnect();
85 | }
86 | }
87 | });
88 | }
89 | super.handlePostClose();
90 | }
91 |
92 | @Override
93 | protected BusHook delegate() {
94 | return hook;
95 | }
96 | });
97 | }
98 |
99 | public void reconnect() {
100 | if (getReadyState() == State.OPEN || getReadyState() == State.CONNECTING) {
101 | return;
102 | }
103 | if (webSocket != null) {
104 | webSocket.close();
105 | }
106 |
107 | connect(serverUri, options);
108 | }
109 |
110 | @Override
111 | public Bus setHook(BusHook hook) {
112 | this.hook = hook;
113 | return this;
114 | }
115 |
116 | @Override
117 | protected void doClose() {
118 | reconnect = false;
119 | backOffGenerator.reset();
120 | queuedMessages.clear();
121 | super.doClose();
122 | }
123 |
124 | @Override
125 | protected void send(JsonObject msg) {
126 | if (getReadyState() == State.OPEN) {
127 | super.send(msg);
128 | return;
129 | }
130 | if (reconnect) {
131 | reconnect();
132 | }
133 | String type = msg.getString(WebSocketBus.TYPE);
134 | if ("ping".equals(type) || "register".equals(type)) {
135 | return;
136 | }
137 | queuedMessages.push(msg);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/Bus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel;
15 |
16 | import com.google.gwt.core.client.js.JsType;
17 |
18 | import com.goodow.realtime.core.Handler;
19 | import com.goodow.realtime.core.Registration;
20 |
21 | /**
22 | * A distributed lightweight event bus which can encompass multiple machines. The event bus
23 | * implements publish/subscribe, point to point messaging and request-response messaging.
24 | * Messages sent over the event bus are represented by instances of the {@link Message} class.
25 | * For publish/subscribe, messages can be published to a topic using one of the {@link #publish}
26 | * methods. A topic is a simple {@code String} instance.
27 | * Handlers are registered against a topic. There can be multiple handlers registered against
28 | * each topic, and a particular handler can be registered against multiple topics. The event
29 | * bus will route a sent message to all handlers which are registered against that topic.
30 | * For point to point messaging, messages can be sent to a topic using one of the {@link #send}
31 | * methods. The messages will be delivered to a single handler, if one is registered on that
32 | * topic. If more than one handler is registered on the same topic, the bus will choose one and
33 | * deliver the message to that. The bus will aim to fairly distribute messages in a round-robin way,
34 | * but does not guarantee strict round-robin under all circumstances.
35 | * The order of messages received by any specific handler from a specific sender should match the
36 | * order of messages sent from that sender.
37 | * When sending a message, a reply handler can be provided. If so, it will be called when the reply
38 | * from the receiver has been received. Reply messages can also be replied to, etc, ad infinitum
39 | * Different event bus instances can be clustered together over a network, to give a single logical
40 | * event bus.
41 | */
42 | @JsType
43 | public interface Bus {
44 | String ON_OPEN = "@realtime/bus/onOpen";
45 | String ON_CLOSE = "@realtime/bus/onClose";
46 | String ON_ERROR = "@realtime/bus/onError";
47 |
48 | /**
49 | * Close the Bus and release all resources.
50 | */
51 | void close();
52 |
53 | /* The state of the Bus. */
54 | State getReadyState();
55 |
56 | /* Returns the session ID used by this bus. */
57 | String getSessionId();
58 |
59 | /**
60 | * Publish a message
61 | *
62 | * @param topic The topic to publish it to
63 | * @param msg The message
64 | */
65 | Bus publish(String topic, Object msg);
66 |
67 | /**
68 | * Publish a local message
69 | *
70 | * @param topic The topic to publish it to
71 | * @param msg The message
72 | */
73 | Bus publishLocal(String topic, Object msg);
74 |
75 | /**
76 | * Send a message
77 | *
78 | * @param topic The topic to send it to
79 | * @param msg The message
80 | * @param replyHandler Reply handler will be called when any reply from the recipient is received
81 | */
82 | Bus send(String topic, Object msg, Handler> replyHandler);
83 |
84 | /**
85 | * Send a local message
86 | *
87 | * @param topic The topic to send it to
88 | * @param msg The message
89 | * @param replyHandler Reply handler will be called when any reply from the recipient is received
90 | */
91 | Bus sendLocal(String topic, Object msg, Handler> replyHandler);
92 |
93 | /**
94 | * Set a BusHook on the Bus
95 | *
96 | * @param hook The hook
97 | */
98 | Bus setHook(BusHook hook);
99 |
100 | /**
101 | * Registers a handler against the specified topic
102 | *
103 | * @param topic The topic to register it at
104 | * @param handler The handler
105 | * @return the handler registration, can be stored in order to unregister the handler later
106 | */
107 | @SuppressWarnings("rawtypes")
108 | Registration subscribe(String topic, Handler extends Message> handler);
109 |
110 | /**
111 | * Registers a local handler against the specified topic. The handler info won't be propagated
112 | * across the cluster
113 | *
114 | * @param topic The topic to register it at
115 | * @param handler The handler
116 | */
117 | @SuppressWarnings("rawtypes")
118 | Registration subscribeLocal(String topic, Handler extends Message> handler);
119 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/server/impl/VertxBus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.server.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.BusHook;
18 | import com.goodow.realtime.channel.Message;
19 | import com.goodow.realtime.channel.State;
20 | import com.goodow.realtime.channel.impl.SimpleBus;
21 | import com.goodow.realtime.core.Handler;
22 | import com.goodow.realtime.core.Registration;
23 | import com.goodow.realtime.json.impl.JreJsonArray;
24 | import com.goodow.realtime.json.impl.JreJsonObject;
25 |
26 | import org.vertx.java.core.AsyncResult;
27 | import org.vertx.java.core.eventbus.EventBus;
28 | import org.vertx.java.core.json.JsonArray;
29 | import org.vertx.java.core.json.JsonObject;
30 |
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 |
34 | public class VertxBus implements Bus {
35 | private static final Logger log = Logger.getLogger(VertxBus.class.getName());
36 |
37 | @SuppressWarnings("unchecked")
38 | static Object unwrapMsg(Object vertxMessage) {
39 | if (vertxMessage instanceof JsonObject) {
40 | return new JreJsonObject(((JsonObject) vertxMessage).toMap());
41 | } else if (vertxMessage instanceof JsonArray) {
42 | return new JreJsonArray(((JsonArray) vertxMessage).toList());
43 | } else {
44 | return vertxMessage;
45 | }
46 | }
47 |
48 | static Object wrapMsg(Object realtimeMessage) {
49 | if (realtimeMessage instanceof JreJsonObject) {
50 | return new JsonObject(((JreJsonObject) realtimeMessage).toNative());
51 | } else if (realtimeMessage instanceof JreJsonArray) {
52 | return new JsonArray(((JreJsonArray) realtimeMessage).toNative());
53 | } else {
54 | return realtimeMessage;
55 | }
56 | }
57 |
58 | private final Bus localBus;
59 | private final EventBus eb;
60 | private State state;
61 | private BusHook hook;
62 |
63 | public VertxBus(EventBus eb) {
64 | this.eb = eb;
65 | state = State.OPEN;
66 | localBus = new SimpleBus();
67 | }
68 |
69 | @Override
70 | public void close() {
71 | if (hook == null || hook.handlePreClose()) {
72 | state = State.CLOSING;
73 | localBus.close();
74 | eb.close(new org.vertx.java.core.Handler>() {
75 | @Override
76 | public void handle(AsyncResult ar) {
77 | if (ar.succeeded()) {
78 | state = State.CLOSED;
79 | if (hook != null) {
80 | hook.handlePostClose();
81 | }
82 | } else {
83 | log.log(Level.SEVERE, "Failed to close EventBus", ar.cause());
84 | }
85 | }
86 | });
87 | }
88 | }
89 |
90 | @Override
91 | public State getReadyState() {
92 | return state;
93 | }
94 |
95 | @Override
96 | public String getSessionId() {
97 | return "vertx";
98 | }
99 |
100 | @Override
101 | public VertxBus publish(String topic, Object msg) {
102 | if (hook == null || hook.handleSendOrPub(false, topic, msg, null)) {
103 | eb.publish(topic, wrapMsg(msg));
104 | }
105 | return this;
106 | }
107 |
108 | @Override
109 | public Bus publishLocal(String topic, Object msg) {
110 | return localBus.publishLocal(topic, msg);
111 | }
112 |
113 | @SuppressWarnings("rawtypes")
114 | @Override
115 | public Registration subscribe(final String topic,
116 | final Handler extends Message> handler) {
117 | if (hook != null && !hook.handlePreSubscribe(topic, handler)) {
118 | return Registration.EMPTY;
119 | }
120 | final org.vertx.java.core.Handler vertxHandler =
121 | wrapHandler(handler);
122 | eb.registerHandler(topic, vertxHandler, new org.vertx.java.core.Handler>() {
123 | @Override
124 | public void handle(AsyncResult ar) {
125 | if (ar.failed()) {
126 | log.log(Level.SEVERE, "Failed to register handler on event bus", ar.cause());
127 | }
128 | }
129 | });
130 | return new Registration() {
131 | @Override
132 | public void unregister() {
133 | if (hook == null || hook.handleUnsubscribe(topic)) {
134 | eb.unregisterHandler(topic, vertxHandler,
135 | new org.vertx.java.core.Handler>() {
136 | @Override
137 | public void handle(AsyncResult ar) {
138 | if (ar.failed()) {
139 | log.log(Level.SEVERE, "Failed to unregister handler on event bus", ar.cause());
140 | }
141 | }
142 | });
143 | }
144 | }
145 | };
146 | }
147 |
148 | @SuppressWarnings("rawtypes")
149 | @Override
150 | public Registration subscribeLocal(final String topic,
151 | Handler extends Message> handler) {
152 | return localBus.subscribeLocal(topic, handler);
153 | }
154 |
155 | @Override
156 | public VertxBus send(String topic, Object msg, final Handler> replyHandler) {
157 | if (hook == null || hook.handleSendOrPub(true, topic, msg, replyHandler)) {
158 | eb.send(topic, wrapMsg(msg), wrapHandler(replyHandler));
159 | }
160 | return this;
161 | }
162 |
163 | @Override
164 | public Bus sendLocal(String topic, Object msg, Handler> replyHandler) {
165 | return localBus.sendLocal(topic, msg, replyHandler);
166 | }
167 |
168 | @Override
169 | public VertxBus setHook(BusHook hook) {
170 | this.hook = hook;
171 | if (hook != null && state == State.OPEN) {
172 | hook.handleOpened();
173 | }
174 | return this;
175 | }
176 |
177 | BusHook getHook() {
178 | return hook;
179 | }
180 |
181 | @SuppressWarnings("rawtypes")
182 | private org.vertx.java.core.Handler wrapHandler(
183 | final Handler extends Message> handler) {
184 | return handler == null ? null
185 | : new org.vertx.java.core.Handler() {
186 | @SuppressWarnings("unchecked")
187 | @Override
188 | public void handle(org.vertx.java.core.eventbus.Message message) {
189 | VertxMessage event = new VertxMessage(VertxBus.this, message);
190 | if (hook == null || hook.handleReceiveMessage(event)) {
191 | ((Handler) handler).handle(event);
192 | }
193 | }
194 | };
195 | }
196 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | com.goodow.realtime
5 | realtime-parent
6 | 0.5.5-SNAPSHOT
7 |
8 | realtime-channel
9 | https://github.com/goodow/realtime-channel/
10 | 2013
11 |
12 |
13 | 1.6
14 | 1.6
15 |
16 | false
17 | false
18 |
19 |
20 |
21 |
22 | sonatype-nexus-snapshots
23 | Sonatype Nexus Snapshots
24 | https://oss.sonatype.org/content/repositories/snapshots
25 |
26 | false
27 |
28 |
29 | true
30 |
31 |
32 |
33 | google-diff-match-patch
34 | http://google-diff-match-patch.googlecode.com/svn/trunk/maven/
35 |
36 | false
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | com.goodow.realtime
45 | realtime-json
46 | ${project.version}
47 |
48 |
49 | com.goodow.realtime
50 | realtime-json
51 | ${project.version}
52 | sources
53 | provided
54 |
55 |
56 |
57 |
58 | io.vertx
59 | vertx-platform
60 |
61 |
62 |
63 | diff_match_patch
64 | diff_match_patch
65 | current
66 |
67 |
68 |
69 |
70 | io.vertx
71 | testtools
72 |
73 |
74 |
75 |
76 | com.google.gwt
77 | gwt-user
78 |
79 |
80 | com.google.gwt
81 | gwt-codeserver
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | io.vertx
90 | vertx-maven-plugin
91 |
92 | src/main/resources/channel.conf
93 |
94 |
95 |
96 | PullInDeps
97 | prepare-package
98 |
99 | pullInDeps
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | maven-resources-plugin
108 |
109 |
110 | copy-mod-to-target
111 | process-classes
112 |
113 | copy-resources
114 |
115 |
116 | true
117 | ${vertx.mods.directory}/${vertx.module.name}
118 |
119 |
120 | target/classes
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | maven-dependency-plugin
129 |
130 |
131 | copy-mod-dependencies-to-target
132 | process-classes
133 |
134 | copy-dependencies
135 |
136 |
137 | ${vertx.mods.directory}/${vertx.module.name}/lib
138 | runtime
139 | com.goodow.realtime,com.fasterxml.jackson.core
140 |
141 |
142 |
143 | copy-mod-dependencies-to-target-dependencies
144 | process-classes
145 |
146 | copy-dependencies
147 |
148 |
149 | target/dependencies
150 | runtime
151 |
152 |
153 |
154 |
155 |
156 | maven-assembly-plugin
157 |
158 |
159 | src/main/assembly/mod.xml
160 |
161 |
162 |
163 |
164 | assemble
165 | package
166 |
167 | single
168 |
169 |
170 |
171 |
172 |
173 | maven-jar-plugin
174 |
175 |
176 | mod.json
177 |
178 |
179 |
180 |
181 |
182 | maven-source-plugin
183 |
184 |
185 |
186 | org.codehaus.mojo
187 | gwt-maven-plugin
188 |
189 |
190 |
191 |
192 | 0.0.0.0
193 | ${project.groupId}.channel.Channel
194 | false
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | org.eclipse.m2e
204 | lifecycle-mapping
205 | 1.0.0
206 |
207 |
208 |
209 |
210 |
211 | org.apache.maven.plugins
212 | maven-dependency-plugin
213 | [2.7,)
214 |
215 | copy-dependencies
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/WebSocketBus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.Message;
18 | import com.goodow.realtime.channel.State;
19 | import com.goodow.realtime.core.Handler;
20 | import com.goodow.realtime.core.Platform;
21 | import com.goodow.realtime.core.WebSocket;
22 | import com.goodow.realtime.json.Json;
23 | import com.goodow.realtime.json.JsonObject;
24 |
25 | @SuppressWarnings("rawtypes")
26 | public class WebSocketBus extends SimpleBus {
27 | public static final String SESSION = "_session";
28 | public static final String USERNAME = "username";
29 | public static final String PASSWORD = "password";
30 | public static final String PING_INTERVAL = "vertxbus_ping_interval";
31 | public static final String TOPIC_CHANNEL = "realtime/channel";
32 | public static final String TOPIC_CONNECT = TOPIC_CHANNEL + "/_CONNECT";
33 |
34 | protected static final String BODY = "body";
35 | protected static final String TOPIC = "address";
36 | protected static final String REPLY_TOPIC = "replyAddress";
37 | protected static final String TYPE = "type";
38 |
39 | private final WebSocket.WebSocketHandler webSocketHandler;
40 | String serverUri;
41 | WebSocket webSocket;
42 | private int pingInterval;
43 | private int pingTimerID = -1;
44 | private String sessionId;
45 | private String username;
46 | private String password;
47 | final JsonObject handlerCount = Json.createObject();
48 |
49 | public WebSocketBus(String serverUri, JsonObject options) {
50 | webSocketHandler = new WebSocket.WebSocketHandler() {
51 | @Override
52 | public void onClose(JsonObject reason) {
53 | Platform.scheduler().cancelTimer(pingTimerID);
54 | publishLocal(ON_CLOSE, reason);
55 | if (hook != null) {
56 | hook.handlePostClose();
57 | }
58 | }
59 |
60 | @Override
61 | public void onError(String error) {
62 | publishLocal(ON_ERROR, Json.createObject().set("message", error));
63 | }
64 |
65 | @Override
66 | public void onMessage(String msg) {
67 | JsonObject json = Json. parse(msg);
68 | @SuppressWarnings({"unchecked"})
69 | MessageImpl message =
70 | new MessageImpl(false, false, WebSocketBus.this, json.getString(TOPIC), json
71 | .getString(REPLY_TOPIC), json.get(BODY));
72 | internalHandleReceiveMessage(message);
73 | }
74 |
75 | @Override
76 | public void onOpen() {
77 | sendConnect();
78 | // Send the first ping then send a ping every 5 seconds
79 | sendPing();
80 | pingTimerID = Platform.scheduler().schedulePeriodic(pingInterval, new Handler() {
81 | @Override
82 | public void handle(Void ignore) {
83 | sendPing();
84 | }
85 | });
86 | if (hook != null) {
87 | hook.handleOpened();
88 | }
89 | publishLocal(ON_OPEN, null);
90 | }
91 | };
92 |
93 | connect(serverUri, options);
94 | }
95 |
96 | public void connect(String serverUri, JsonObject options) {
97 | this.serverUri = serverUri;
98 | pingInterval =
99 | options == null || !options.has(PING_INTERVAL) ? 5 * 1000 : (int) options
100 | .getNumber(PING_INTERVAL);
101 | sessionId = options == null || !options.has(SESSION) ? idGenerator.next(23) : options.getString(
102 | SESSION);
103 | username = options == null || !options.has(USERNAME) ? null : options.getString(USERNAME);
104 | password = options == null || !options.has(PASSWORD) ? null : options.getString(PASSWORD);
105 |
106 | webSocket = Platform.net().createWebSocket(serverUri, options);
107 | webSocket.setListen(webSocketHandler);
108 | }
109 |
110 | @Override
111 | public State getReadyState() {
112 | return webSocket.getReadyState();
113 | }
114 |
115 | @Override
116 | public String getSessionId() {
117 | return sessionId;
118 | }
119 |
120 | @Override
121 | protected void doClose() {
122 | subscribeLocal(Bus.ON_CLOSE, new Handler>() {
123 | @Override
124 | public void handle(Message event) {
125 | clearHandlers();
126 | handlerCount.clear();
127 | }
128 | });
129 | webSocket.close();
130 | }
131 |
132 | @Override
133 | protected boolean doSubscribe(boolean local, String topic,
134 | Handler extends Message> handler) {
135 | boolean subscribed = super.doSubscribe(local, topic, handler);
136 | if (local || !subscribed || (hook != null && !hook.handlePreSubscribe(topic, handler))) {
137 | return false;
138 | }
139 | if (handlerCount.has(topic)) {
140 | handlerCount.set(topic, handlerCount.getNumber(topic) + 1);
141 | return false;
142 | }
143 | handlerCount.set(topic, 1);
144 | sendSubscribe(topic);
145 | return true;
146 | }
147 |
148 | @Override
149 | protected void doSendOrPub(boolean local, boolean send, String topic, Object msg,
150 | Handler> replyHandler) {
151 | checkNotNull(TOPIC, topic);
152 | if (local) {
153 | super.doSendOrPub(local, send, topic, msg, replyHandler);
154 | return;
155 | }
156 | JsonObject envelope = Json.createObject().set(TYPE, send ? "send" : "publish");
157 | envelope.set(TOPIC, topic).set(BODY, msg);
158 | if (replyHandler != null) {
159 | String replyTopic = makeUUID();
160 | envelope.set(REPLY_TOPIC, replyTopic);
161 | replyHandlers.set(replyTopic, replyHandler);
162 | }
163 | send(envelope);
164 | }
165 |
166 | @Override
167 | protected boolean doUnsubscribe(boolean local, String topic,
168 | Handler extends Message> handler) {
169 | boolean unsubscribed = super.doUnsubscribe(local, topic, handler);
170 | if (local || !unsubscribed || (hook != null && !hook.handleUnsubscribe(topic))) {
171 | return false;
172 | }
173 | handlerCount.set(topic, handlerCount.getNumber(topic) - 1);
174 | if (handlerCount.getNumber(topic) == 0) {
175 | handlerCount.remove(topic);
176 | sendUnsubscribe(topic);
177 | return true;
178 | }
179 | return false;
180 | }
181 |
182 | protected void send(JsonObject msg) {
183 | if (getReadyState() != State.OPEN) {
184 | throw new IllegalStateException("INVALID_STATE_ERR");
185 | }
186 | webSocket.send(msg.toJsonString());
187 | }
188 |
189 | protected void sendConnect() {
190 | JsonObject msg = Json.createObject().set(SESSION, sessionId);
191 | if(username != null) {
192 | msg.set(USERNAME, username);
193 | if(password != null) {
194 | msg.set(PASSWORD, password);
195 | }
196 | }
197 | send(TOPIC_CONNECT, msg, new Handler>() {
198 | @Override
199 | public void handle(Message message) {
200 | if (message.body() != null && message.body().getNumber("code") != 0) {
201 | throw new RuntimeException(message.body().toJsonString());
202 | }
203 | }
204 | });
205 | }
206 |
207 | protected void sendPing() {
208 | send(Json.createObject().set(TYPE, "ping"));
209 | }
210 |
211 | /*
212 | * First handler for this topic so we should register the connection
213 | */
214 | protected void sendSubscribe(String topic) {
215 | assert topic != null : "topic shouldn't be null";
216 | JsonObject msg = Json.createObject().set(TYPE, "register").set(TOPIC, topic);
217 | send(msg);
218 | }
219 |
220 | /*
221 | * No more handlers so we should unregister the connection
222 | */
223 | protected void sendUnsubscribe(String topic) {
224 | JsonObject msg = Json.createObject().set(TYPE, "unregister").set(TOPIC, topic);
225 | send(msg);
226 | }
227 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/SimpleBus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.BusHook;
18 | import com.goodow.realtime.channel.Message;
19 | import com.goodow.realtime.channel.State;
20 | import com.goodow.realtime.channel.util.IdGenerator;
21 | import com.goodow.realtime.core.Handler;
22 | import com.goodow.realtime.core.Platform;
23 | import com.goodow.realtime.core.Registration;
24 | import com.goodow.realtime.json.Json;
25 | import com.goodow.realtime.json.JsonArray;
26 | import com.goodow.realtime.json.JsonObject;
27 |
28 | import java.util.logging.Level;
29 | import java.util.logging.Logger;
30 |
31 | @SuppressWarnings("rawtypes")
32 | public class SimpleBus implements Bus {
33 | private static final Logger log = Logger.getLogger(SimpleBus.class.getName());
34 |
35 | static void checkNotNull(String paramName, Object param) {
36 | if (param == null) {
37 | throw new IllegalArgumentException("Parameter " + paramName + " must be specified");
38 | }
39 | }
40 |
41 | private JsonObject handlerMap; // LinkedHashMap>>
42 | final JsonObject replyHandlers; // LinkedHashMap>
43 | BusHook hook;
44 | final IdGenerator idGenerator;
45 |
46 | public SimpleBus() {
47 | handlerMap = Json.createObject();
48 | replyHandlers = Json.createObject();
49 | idGenerator = new IdGenerator();
50 | }
51 |
52 | @Override
53 | public void close() {
54 | if (hook == null || hook.handlePreClose()) {
55 | doClose();
56 | }
57 | }
58 |
59 | @Override
60 | public State getReadyState() {
61 | return handlerMap == null ? State.CLOSED : State.OPEN;
62 | }
63 |
64 | @Override
65 | public String getSessionId() {
66 | return "@";
67 | }
68 |
69 | @Override
70 | public Bus publish(String topic, Object msg) {
71 | internalHandleSendOrPub(false, false, topic, msg, null);
72 | return this;
73 | }
74 |
75 | @Override
76 | public Bus publishLocal(String topic, Object msg) {
77 | internalHandleSendOrPub(true, false, topic, msg, null);
78 | return this;
79 | }
80 |
81 | @Override
82 | public Registration subscribe(final String topic,
83 | final Handler extends Message> handler) {
84 | return subscribeImpl(false, topic, handler);
85 | }
86 |
87 | @Override
88 | public Registration subscribeLocal(final String topic,
89 | final Handler extends Message> handler) {
90 | return subscribeImpl(true, topic, handler);
91 | }
92 |
93 | @Override
94 | public Bus send(String topic, Object msg, Handler> replyHandler) {
95 | internalHandleSendOrPub(false, true, topic, msg, replyHandler);
96 | return this;
97 | }
98 |
99 | @Override
100 | public Bus sendLocal(String topic, Object msg, Handler> replyHandler) {
101 | internalHandleSendOrPub(true, true, topic, msg, replyHandler);
102 | return this;
103 | }
104 |
105 | @Override
106 | public Bus setHook(BusHook hook) {
107 | this.hook = hook;
108 | return this;
109 | }
110 |
111 | protected void doClose() {
112 | publishLocal(ON_CLOSE, null);
113 | clearHandlers();
114 | if (hook != null) {
115 | hook.handlePostClose();
116 | }
117 | }
118 |
119 | protected boolean doSubscribe(boolean local, String topic,
120 | Handler extends Message> handler) {
121 | checkNotNull("topic", topic);
122 | checkNotNull("handler", handler);
123 | JsonArray handlers = handlerMap.getArray(topic);
124 | if (handlers == null) {
125 | handlerMap.set(topic, Json.createArray().push(handler));
126 | return true;
127 | }
128 | if (handlers.indexOf(handler) == -1) {
129 | handlers.push(handler);
130 | return true;
131 | }
132 | return false;
133 | }
134 |
135 | @SuppressWarnings("unchecked")
136 | protected void doSendOrPub(boolean local, boolean send, String topic, Object msg,
137 | Handler> replyHandler) {
138 | checkNotNull("topic", topic);
139 | String replyTopic = null;
140 | if (replyHandler != null) {
141 | replyTopic = makeUUID();
142 | replyHandlers.set(replyTopic, replyHandler);
143 | }
144 | MessageImpl message = new MessageImpl(local, send, this, topic, replyTopic, msg);
145 | if (!internalHandleReceiveMessage(message) && replyTopic != null) {
146 | replyHandlers.remove(replyTopic);
147 | }
148 | }
149 |
150 | protected boolean doUnsubscribe(boolean local, String topic,
151 | Handler extends Message> handler) {
152 | checkNotNull("topic", topic);
153 | checkNotNull("handler", handler);
154 | JsonArray handlers = handlerMap.getArray(topic);
155 | if (handlers == null) {
156 | return false;
157 | }
158 | boolean removed = handlers.removeValue(handler);
159 | if (handlers.length() == 0) {
160 | handlerMap.remove(topic);
161 | }
162 | return removed;
163 | }
164 |
165 | void clearHandlers() {
166 | replyHandlers.clear();
167 | handlerMap.clear();
168 | handlerMap = null;
169 | }
170 |
171 | boolean internalHandleReceiveMessage(Message message) {
172 | if (message.isLocal() || hook == null || hook.handleReceiveMessage(message)) {
173 | doReceiveMessage(message);
174 | return true;
175 | }
176 | return false;
177 | }
178 |
179 | void internalHandleSendOrPub(boolean local, boolean send, String topic, Object msg,
180 | Handler> replyHandler) {
181 | if (local || hook == null || hook.handleSendOrPub(send, topic, msg, replyHandler)) {
182 | doSendOrPub(local, send, topic, msg, replyHandler);
183 | }
184 | }
185 |
186 | String makeUUID() {
187 | return idGenerator.next(36);
188 | }
189 |
190 | private void doReceiveMessage(final Message message) {
191 | final String topic = message.topic();
192 | JsonArray handlers = handlerMap.getArray(topic);
193 | if (handlers != null) {
194 | // We make a copy since the handler might get unregistered from within the handler itself,
195 | // which would screw up our iteration
196 | final JsonArray copy = Json.createArray();
197 | handlers.forEach(new JsonArray.ListIterator() {
198 | @Override
199 | public void call(int index, Object value) {
200 | copy.push(value);
201 | }
202 | });
203 | copy.forEach(new JsonArray.ListIterator() {
204 | @Override
205 | public void call(int index, Object value) {
206 | scheduleHandle(topic, value, message);
207 | }
208 | });
209 | } else {
210 | // Might be a reply message
211 | Object handler = replyHandlers.get(topic);
212 | if (handler != null) {
213 | replyHandlers.remove(topic);
214 | scheduleHandle(topic, handler, message);
215 | }
216 | }
217 | }
218 |
219 | private void handle(String topic, Object handler, Object message) {
220 | try {
221 | Platform.scheduler().handle(handler, message);
222 | } catch (Throwable e) {
223 | log.log(Level.WARNING, "Failed to handle on topic: " + topic, e);
224 | publishLocal(ON_ERROR, Json.createObject().set("topic", topic)
225 | .set("message", message).set("cause", e));
226 | }
227 | }
228 |
229 | private void scheduleHandle(final String topic, final Object handler, final Message message) {
230 | if (message.isLocal()) {
231 | handle(topic, handler, message);
232 | } else {
233 | Platform.scheduler().scheduleDeferred(new Handler() {
234 | @Override
235 | public void handle(Void ignore) {
236 | SimpleBus.this.handle(topic, handler, message);
237 | }
238 | });
239 | }
240 | }
241 |
242 | private Registration subscribeImpl(final boolean local, final String topic,
243 | final Handler extends Message> handler) {
244 | doSubscribe(local, topic, handler);
245 | return new Registration() {
246 | @Override
247 | public void unregister() {
248 | doUnsubscribe(local, topic, handler);
249 | }
250 | };
251 | }
252 | }
--------------------------------------------------------------------------------
/src/main/java/com/goodow/realtime/channel/impl/ReliableSubscribeBus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel.impl;
15 |
16 | import com.goodow.realtime.channel.Bus;
17 | import com.goodow.realtime.channel.BusHook;
18 | import com.goodow.realtime.channel.Message;
19 | import com.goodow.realtime.core.Handler;
20 | import com.goodow.realtime.core.Platform;
21 | import com.goodow.realtime.json.Json;
22 | import com.goodow.realtime.json.JsonArray;
23 | import com.goodow.realtime.json.JsonObject;
24 |
25 | import java.util.logging.Level;
26 | import java.util.logging.Logger;
27 |
28 | /**
29 | * Converts a stream of possibly-missing, possibly-unordered, possibly-duplicated messages into a
30 | * stream of in-order, consecutive, no-dup messages.
31 | */
32 | public class ReliableSubscribeBus extends BusProxy {
33 | public static final String SEQUENCE_NUMBER = "sequence_number_key";
34 | public static final String PUBLISH_CHANNEL = "publish_channel";
35 | public static final String ACKNOWLEDGE_DELAY_MILLIS = "acknowledgeDelayMillis";
36 |
37 | private static final Logger log = Logger.getLogger(ReliableSubscribeBus.class.getName());
38 | private final String sequenceNumberKey;
39 | private final String publishChannel;
40 | /**
41 | * Delay acknowledgment in case we receive operations in the meantime.
42 | */
43 | private final int acknowledgeDelayMillis;
44 | private final JsonObject pendings; // {topic: {sequence: Message>}}
45 | private final JsonObject currentSequences;
46 | private final JsonObject knownHeadSequences;
47 | private final JsonObject acknowledgeScheduled;
48 | private final JsonObject acknowledgeNumbers;
49 |
50 | public ReliableSubscribeBus(Bus delegate, JsonObject options) {
51 | super(delegate);
52 | sequenceNumberKey =
53 | options == null || !options.has(SEQUENCE_NUMBER) ? "v" : options.getString(SEQUENCE_NUMBER);
54 | publishChannel =
55 | options == null || !options.has(PUBLISH_CHANNEL) ? "realtime/store" : options
56 | .getString(PUBLISH_CHANNEL);
57 | acknowledgeDelayMillis =
58 | options == null || !options.has(ACKNOWLEDGE_DELAY_MILLIS) ? 3 * 1000 : (int) options
59 | .getNumber(ACKNOWLEDGE_DELAY_MILLIS);
60 | pendings = Json.createObject();
61 | currentSequences = Json.createObject();
62 | knownHeadSequences = Json.createObject();
63 | acknowledgeScheduled = Json.createObject();
64 | acknowledgeNumbers = Json.createObject();
65 |
66 | delegate.setHook(new BusHookProxy() {
67 | @Override
68 | public boolean handleReceiveMessage(Message> message) {
69 | if (hook != null && !hook.handleReceiveMessage(message)) {
70 | return false;
71 | }
72 | return onReceiveMessage(message);
73 | }
74 |
75 | @Override
76 | public boolean handleUnsubscribe(String topic) {
77 | if (needProcess(topic)) {
78 | pendings.remove(topic);
79 | currentSequences.remove(topic);
80 | knownHeadSequences.remove(topic);
81 | acknowledgeScheduled.remove(topic);
82 | acknowledgeNumbers.remove(topic);
83 | }
84 | return super.handleUnsubscribe(topic);
85 | }
86 |
87 | @Override
88 | protected BusHook delegate() {
89 | return hook;
90 | }
91 | });
92 | }
93 |
94 | @Override
95 | public void close() {
96 | super.close();
97 | pendings.clear();
98 | currentSequences.clear();
99 | knownHeadSequences.clear();
100 | acknowledgeScheduled.clear();
101 | acknowledgeNumbers.clear();
102 | }
103 |
104 | public void synchronizeSequenceNumber(String topic, double initialSequenceNumber) {
105 | assert !currentSequences.has(topic) && !knownHeadSequences.has(topic)
106 | && !pendings.has(topic);
107 | initSequenceNumber(topic, initialSequenceNumber);
108 | // Send the first acknowledgment immediately, to quickly catch up any initial missing messages,
109 | // which might happen if the topic is currently active.
110 | catchup(topic, initialSequenceNumber);
111 | }
112 |
113 | protected void catchup(final String topic, double currentSequence) {
114 | String id = topic.substring(publishChannel.length() + 1);
115 | id = id.substring(0, id.lastIndexOf("/_watch"));
116 | delegate.send(publishChannel + "/_ops",
117 | Json.createObject().set("id", id) .set("from", currentSequence + 1),
118 | new Handler>() {
119 | @SuppressWarnings({"rawtypes", "unchecked"})
120 | @Override
121 | public void handle(Message message) {
122 | final String replyTopic = message.replyTopic();
123 | message.body().forEach(new JsonArray.ListIterator() {
124 | @Override
125 | public void call(int index, Object value) {
126 | onReceiveMessage(new MessageImpl(false, false, ReliableSubscribeBus.this,
127 | topic, replyTopic, value));
128 | }
129 | });
130 | }
131 | });
132 | }
133 |
134 | protected double getSequenceNumber(String topic, Object body) {
135 | return ((JsonObject) body).getNumber(sequenceNumberKey);
136 | }
137 |
138 | protected boolean needProcess(String topic) {
139 | return topic.startsWith(publishChannel + "/") && topic.endsWith("/_watch") &&
140 | !topic.contains("/_presence/");
141 | }
142 |
143 | protected boolean onReceiveMessage(Message> message) {
144 | String topic = message.topic();
145 | Object body = message.body();
146 | if (!needProcess(topic)) {
147 | return true;
148 | }
149 | double sequence = getSequenceNumber(topic, body);
150 | if (!currentSequences.has(topic)) {
151 | initSequenceNumber(topic, sequence);
152 | return true;
153 | }
154 |
155 | double currentSequence = currentSequences.getNumber(topic);
156 | if (sequence <= currentSequence) {
157 | log.log(Level.CONFIG, "Old dup at sequence " + sequence + ", current is now "
158 | + currentSequence);
159 | return false;
160 | }
161 | JsonObject pending = pendings.getObject(topic);
162 | Message> existing = pending.get("" + sequence);
163 | if (existing != null) {
164 | // Should not have pending data at a sequence we could have pushed out.
165 | assert sequence > currentSequence + 1 : "should not have pending data";
166 | log.log(Level.CONFIG, "Dup message: " + message);
167 | return false;
168 | }
169 |
170 | knownHeadSequences.set(topic, Math.max(knownHeadSequences.getNumber(topic), sequence));
171 |
172 | if (sequence > currentSequence + 1) {
173 | pending.set("" + sequence, message);
174 | log.log(Level.CONFIG, "Missed message, current sequence=" + currentSequence
175 | + " incoming sequence=" + sequence);
176 | scheduleAcknowledgment(topic);
177 | return false;
178 | }
179 |
180 | assert sequence == currentSequence + 1 : "other cases should have been caught";
181 | String next;
182 | JsonArray messages = Json.createArray();
183 | while (true) {
184 | messages.push(message);
185 | currentSequences.set(topic, ++currentSequence);
186 | next = currentSequence + 1 + "";
187 | message = pending.get(next);
188 | if (message != null) {
189 | pending.remove(next);
190 | } else {
191 | break;
192 | }
193 | }
194 | scheduleMessages(messages);
195 | assert !pending.has(next);
196 | return false;
197 | }
198 |
199 | private void initSequenceNumber(String topic, double initialSequenceNumber) {
200 | currentSequences.set(topic, initialSequenceNumber);
201 | knownHeadSequences.set(topic, initialSequenceNumber);
202 | pendings.set(topic, Json.createObject());
203 | }
204 |
205 | /**
206 | * Acknowledgment Number is the next sequence number that the receiver is expecting
207 | */
208 | private void scheduleAcknowledgment(final String topic) {
209 | if (!acknowledgeScheduled.has(topic)) {
210 | acknowledgeScheduled.set(topic, true);
211 | Platform.scheduler().scheduleDelay(acknowledgeDelayMillis, new Handler() {
212 | @Override
213 | public void handle(Void event) {
214 | if (acknowledgeScheduled.has(topic)) {
215 | acknowledgeScheduled.remove(topic);
216 | // Check we're still out of date, and not already catching up.
217 | double knownHeadSequence = knownHeadSequences.getNumber(topic);
218 | double currentSequence = currentSequences.getNumber(topic);
219 | if (knownHeadSequence > currentSequence
220 | && (!acknowledgeNumbers.has(topic) || knownHeadSequence > acknowledgeNumbers
221 | .getNumber(topic))) {
222 | acknowledgeNumbers.set(topic, knownHeadSequence);
223 | log.log(Level.CONFIG, "Catching up to " + knownHeadSequence);
224 | catchup(topic, currentSequence);
225 | } else {
226 | log.log(Level.FINE, "No need to catchup");
227 | }
228 | }
229 | }
230 | });
231 | }
232 | }
233 |
234 | private void scheduleMessages(final JsonArray messages) {
235 | Platform.scheduler().scheduleDeferred(new Handler() {
236 | @Override
237 | public void handle(Void event) {
238 | messages.forEach(new JsonArray.ListIterator>() {
239 | @Override
240 | public void call(int index, Message> message) {
241 | delegate.publishLocal(message.topic(), message.body());
242 | }
243 | });
244 | }
245 | });
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/src/main/resources/com/google/gwt/core/linker/SingleScriptTemplate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2008 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 | * use this file except in compliance with the License. You may obtain a copy of
6 | * the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | function __MODULE_FUNC__() {
18 | // ---------------- INTERNAL GLOBALS ----------------
19 |
20 | // Cache symbols locally for good obfuscation
21 | var $wnd = window
22 | ,$doc = document
23 |
24 | // These variables gate calling gwtOnLoad; all must be true to start
25 | ,gwtOnLoad, bodyDone
26 |
27 | // If non-empty, an alternate base url for this module
28 | ,base = ''
29 |
30 | // A map of properties that were declared in meta tags
31 | ,metaProps = {}
32 |
33 | // Maps property names onto sets of legal values for that property.
34 | ,values = []
35 |
36 | // Maps property names onto a function to compute that property.
37 | ,providers = []
38 |
39 | // A multi-tier lookup map that uses actual property values to quickly find
40 | // the strong name of the cache.js file to load.
41 | ,answers = []
42 |
43 | // Provides the module with the soft permutation id
44 | ,softPermutationId = 0
45 |
46 | // Error functions. Default unset in compiled mode, may be set by meta props.
47 | ,onLoadErrorFunc, propertyErrorFunc
48 |
49 | ; // end of global vars
50 |
51 | // ------------------ TRUE GLOBALS ------------------
52 |
53 | // Maps to synchronize the loading of styles and scripts; resources are loaded
54 | // only once, even when multiple modules depend on them. This API must not
55 | // change across GWT versions.
56 | if (!$wnd.__gwt_stylesLoaded) { $wnd.__gwt_stylesLoaded = {}; }
57 | if (!$wnd.__gwt_scriptsLoaded) { $wnd.__gwt_scriptsLoaded = {}; }
58 |
59 | // --------------- INTERNAL FUNCTIONS ---------------
60 |
61 | function isHostedMode() {
62 | var result = false;
63 | try {
64 | var query = $wnd.location.search;
65 | return (query.indexOf('gwt.codesvr=') != -1
66 | || query.indexOf('gwt.hosted=') != -1
67 | || ($wnd.external && $wnd.external.gwtOnLoad)) &&
68 | (query.indexOf('gwt.hybrid') == -1);
69 | } catch (e) {
70 | // Defensive: some versions of IE7 reportedly can throw an exception
71 | // evaluating "external.gwtOnLoad".
72 | }
73 | isHostedMode = function() { return result; };
74 | return result;
75 | }
76 |
77 | // Called by onScriptLoad() and onload(). It causes
78 | // the specified module to be cranked up.
79 | //
80 | function maybeStartModule() {
81 | // TODO: it may not be necessary to check gwtOnLoad here.
82 | if (gwtOnLoad) {
83 | gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base, softPermutationId);
84 | }
85 | }
86 |
87 | // Determine our own script's URL via magic :)
88 | // This function produces one side-effect, it sets base to the module's
89 | // base url.
90 | //
91 | function computeScriptBase() {
92 | var thisScript
93 | ,markerId = "__gwt_marker___MODULE_NAME__"
94 | ,markerScript;
95 |
96 | $doc.write('');
97 | markerScript = $doc.getElementById(markerId);
98 |
99 | // Our script element is assumed to be the closest previous script element
100 | // to the marker, so start at the marker and walk backwards until we find
101 | // a script.
102 | thisScript = markerScript && markerScript.previousSibling;
103 | while (thisScript && thisScript.tagName != 'SCRIPT') {
104 | thisScript = thisScript.previousSibling;
105 | }
106 |
107 | // Gets the part of a url up to and including the 'path' portion.
108 | function getDirectoryOfFile(path) {
109 | // Truncate starting at the first '?' or '#', whichever comes first.
110 | var hashIndex = path.lastIndexOf('#');
111 | if (hashIndex == -1) {
112 | hashIndex = path.length;
113 | }
114 | var queryIndex = path.indexOf('?');
115 | if (queryIndex == -1) {
116 | queryIndex = path.length;
117 | }
118 | var slashIndex = path.lastIndexOf('/', Math.min(queryIndex, hashIndex));
119 | return (slashIndex >= 0) ? path.substring(0, slashIndex + 1) : '';
120 | };
121 |
122 | if (thisScript && thisScript.src) {
123 | // Compute our base url
124 | base = getDirectoryOfFile(thisScript.src);
125 | }
126 |
127 | // Make the base URL absolute
128 | if (base == '') {
129 | // If there's a base tag, use it.
130 | var baseElements = $doc.getElementsByTagName('base');
131 | if (baseElements.length > 0) {
132 | // It's always the last parsed base tag that will apply to this script.
133 | base = baseElements[baseElements.length - 1].href;
134 | } else {
135 | // No base tag; the base must be the same as the document location.
136 | base = getDirectoryOfFile($doc.location.href);
137 | }
138 | } else if ((base.match(/^\w+:\/\//))) {
139 | // If the URL is obviously absolute, do nothing.
140 | } else {
141 | // Probably a relative URL; use magic to make the browser absolutify it.
142 | // I wish there were a better way to do this, but this seems the only
143 | // sure way! (A side benefit is it preloads clear.cache.gif)
144 | // Note: this trick is harmless if the URL was really already absolute.
145 | var img = $doc.createElement("img");
146 | img.src = base + 'clear.cache.gif';
147 | base = getDirectoryOfFile(img.src);
148 | }
149 |
150 | if (markerScript) {
151 | // remove the marker element
152 | markerScript.parentNode.removeChild(markerScript);
153 | }
154 | }
155 |
156 | // Called to slurp up all tags:
157 | // gwt:property, gwt:onPropertyErrorFn, gwt:onLoadErrorFn
158 | //
159 | function processMetas() {
160 | var metas = document.getElementsByTagName('meta');
161 | for (var i = 0, n = metas.length; i < n; ++i) {
162 | var meta = metas[i], name = meta.getAttribute('name'), content;
163 |
164 | if (name) {
165 | if (name == 'gwt:property') {
166 | content = meta.getAttribute('content');
167 | if (content) {
168 | var value, eq = content.indexOf('=');
169 | if (eq >= 0) {
170 | name = content.substring(0, eq);
171 | value = content.substring(eq+1);
172 | } else {
173 | name = content;
174 | value = '';
175 | }
176 | metaProps[name] = value;
177 | }
178 | } else if (name == 'gwt:onPropertyErrorFn') {
179 | content = meta.getAttribute('content');
180 | if (content) {
181 | try {
182 | propertyErrorFunc = eval(content);
183 | } catch (e) {
184 | alert('Bad handler \"' + content +
185 | '\" for \"gwt:onPropertyErrorFn\"');
186 | }
187 | }
188 | } else if (name == 'gwt:onLoadErrorFn') {
189 | content = meta.getAttribute('content');
190 | if (content) {
191 | try {
192 | onLoadErrorFunc = eval(content);
193 | } catch (e) {
194 | alert('Bad handler \"' + content + '\" for \"gwt:onLoadErrorFn\"');
195 | }
196 | }
197 | }
198 | }
199 | }
200 | }
201 |
202 | /**
203 | * Determines whether or not a particular property value is allowed. Called by
204 | * property providers.
205 | *
206 | * @param propName the name of the property being checked
207 | * @param propValue the property value being tested
208 | */
209 | __gwt_isKnownPropertyValue = function(propName, propValue) {
210 | return propValue in values[propName];
211 | }
212 |
213 | /**
214 | * Returns a meta property value, if any. Used by DefaultPropertyProvider.
215 | */
216 | __gwt_getMetaProperty = function(name) {
217 | var value = metaProps[name];
218 | return (value == null) ? null : value;
219 | }
220 |
221 | // Deferred-binding mapper function. Sets a value into the several-level-deep
222 | // answers map. The keys are specified by a non-zero-length propValArray,
223 | // which should be a flat array target property values. Used by the generated
224 | // PERMUTATIONS code.
225 | //
226 | function unflattenKeylistIntoAnswers(propValArray, value) {
227 | var answer = answers;
228 | for (var i = 0, n = propValArray.length - 1; i < n; ++i) {
229 | // lazy initialize an empty object for the current key if needed
230 | answer = answer[propValArray[i]] || (answer[propValArray[i]] = []);
231 | }
232 | // set the final one to the value
233 | answer[propValArray[n]] = value;
234 | }
235 |
236 | // Computes the value of a given property. propName must be a valid property
237 | // name. Used by the generated PERMUTATIONS code.
238 | //
239 | function computePropValue(propName) {
240 | var value = providers[propName](), allowedValuesMap = values[propName];
241 | if (value in allowedValuesMap) {
242 | return value;
243 | }
244 | var allowedValuesList = [];
245 | for (var k in allowedValuesMap) {
246 | allowedValuesList[allowedValuesMap[k]] = k;
247 | }
248 | if (propertyErrorFunc) {
249 | propertyErrorFunc(propName, allowedValuesList, value);
250 | }
251 | throw null;
252 | }
253 |
254 | // --------------- PROPERTY PROVIDERS ---------------
255 |
256 | // __PROPERTIES_BEGIN__
257 | // __PROPERTIES_END__
258 |
259 | // --------------- EXPOSED FUNCTIONS ----------------
260 |
261 | // Called when the compiled script identified by moduleName is done loading.
262 | //
263 | __MODULE_FUNC__.onScriptLoad = function(gwtOnLoadFunc) {
264 | // remove this whole function from the global namespace to allow GC
265 | __MODULE_FUNC__ = null;
266 | gwtOnLoad = gwtOnLoadFunc;
267 | maybeStartModule();
268 | }
269 |
270 | // --------------- STRAIGHT-LINE CODE ---------------
271 |
272 | if (isHostedMode()) {
273 | alert("Single-script hosted mode not yet implemented. See issue " +
274 | "http://code.google.com/p/google-web-toolkit/issues/detail?id=2079");
275 | return;
276 | }
277 |
278 | // do it early for compile/browse rebasing
279 | computeScriptBase();
280 | processMetas();
281 |
282 | // --------------- WINDOW ONLOAD HOOK ---------------
283 |
284 | try {
285 | var strongName;
286 | // __PERMUTATIONS_BEGIN__
287 | // Permutation logic
288 | // __PERMUTATIONS_END__
289 | var idx = strongName.indexOf(':');
290 | if (idx != -1) {
291 | softPermutationId = Number(strongName.substring(idx + 1));
292 | }
293 | } catch (e) {
294 | // intentionally silent on property failure
295 | return;
296 | }
297 |
298 | /*
299 | var onBodyDoneTimerId;
300 | function onBodyDone() {
301 | if (!bodyDone) {
302 | bodyDone = true;
303 | // __MODULE_STYLES_BEGIN__
304 | // Style resources are injected here to prevent operation aborted errors on ie
305 | // __MODULE_STYLES_END__
306 | maybeStartModule();
307 |
308 | if ($doc.removeEventListener) {
309 | $doc.removeEventListener("DOMContentLoaded", onBodyDone, false);
310 | }
311 | if (onBodyDoneTimerId) {
312 | clearInterval(onBodyDoneTimerId);
313 | }
314 | }
315 | }
316 |
317 | // For everyone that supports DOMContentLoaded.
318 | if ($doc.addEventListener) {
319 | $doc.addEventListener("DOMContentLoaded", function() {
320 | onBodyDone();
321 | }, false);
322 | }
323 |
324 | // Fallback. If onBodyDone() gets fired twice, it's not a big deal.
325 | var onBodyDoneTimerId = setInterval(function() {
326 | if (/loaded|complete/.test($doc.readyState)) {
327 | onBodyDone();
328 | }
329 | }, 50);
330 | */
331 |
332 | // __MODULE_SCRIPTS_BEGIN__
333 | // Script resources are injected here
334 | // __MODULE_SCRIPTS_END__
335 | }
336 |
337 | __MODULE_FUNC__();
338 |
--------------------------------------------------------------------------------