├── build.gradle
├── public
├── app.js
├── index.html
├── main.css
└── vertx-client.js
├── readme.md
└── src
└── main
└── java
└── com
└── rethinkdb
└── demo
└── RethinkDemo.java
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'application'
2 |
3 | repositories {
4 | jcenter()
5 | mavenCentral()
6 | maven {
7 | url "https://oss.sonatype.org/service/local/repositories/snapshots/content"
8 | }
9 | }
10 |
11 | dependencies {
12 | compile (
13 | 'io.vertx:vertx-web:3.1.0',
14 | 'com.rethinkdb:rethinkdb-driver:2.2-b1-SNAPSHOT',
15 | )
16 | }
17 |
18 | mainClassName = "com.rethinkdb.demo.RethinkDemo"
19 |
--------------------------------------------------------------------------------
/public/app.js:
--------------------------------------------------------------------------------
1 | var app = new Vue({
2 | el: "body",
3 | data: {
4 | identified: false,
5 | username: null,
6 | message: null,
7 | messages: []
8 | },
9 | methods: {
10 | connect: function(messages) {
11 | this.messages = messages;
12 | setTimeout(this.scrollBottom);
13 | var bus = new vertx.EventBus("/eventbus");
14 | bus.onopen = () => bus.registerHandler("chat", this.onMessage);
15 | },
16 | scrollBottom: function() {
17 | var el = document.getElementById("messages");
18 | el.scrollTop = el.scrollHeight;
19 | },
20 | onMessage: function(message) {
21 | this.messages.push(message);
22 | setTimeout(this.scrollBottom);
23 | },
24 | sendMessage: function() {
25 | fetch("/send", {
26 | method: "post",
27 | headers: {
28 | "Accept": "application/json",
29 | "Content-Type": "application/json"
30 | },
31 | body: JSON.stringify({
32 | user: this.username,
33 | text: this.message
34 | })
35 | });
36 |
37 | this.message = null;
38 | },
39 | login: function(event) {
40 | this.identified = true;
41 | fetch("/messages").then(res => res.json()).then(this.connect);
42 | }
43 | }
44 | });
45 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | RethinkDB Java Demo
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | RethinkDB Chat Demo
16 |
17 |
18 |
Login
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
{{message.user}}:
27 |
{{message.text}}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/public/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Open Sans", sans-serif;
3 | background: #F9FCFC;
4 | margin: 0;
5 |
6 | height: 100%;
7 | display: flex;
8 | flex-direction: column;
9 | }
10 |
11 | h1 {
12 | background: #184b5a;
13 | padding: 15px;
14 | color: white;
15 | margin: 0;
16 | font-weight: 300;
17 | }
18 |
19 | #chat {
20 | height: 0;
21 | flex-grow: 1;
22 | display: flex;
23 | flex-direction: column;
24 | }
25 |
26 | #messages {
27 | flex-grow: 1;
28 | overflow-y: auto;
29 | }
30 |
31 | .entry {
32 | display: flex;
33 | padding: 5px;
34 | }
35 |
36 | .entry input {
37 | flex-grow: 3;
38 | }
39 |
40 | .entry button {
41 | width: 60px;
42 | margin-left: 5px;
43 | }
44 |
45 | input, button {
46 | padding: 5px;
47 | font-family: "Open Sans", sans-serif !important;
48 | }
49 |
50 | #login {
51 | padding: 15px;
52 | }
53 |
54 | #login input {
55 | width: 300px;
56 | }
57 |
58 | .message {
59 | margin: 10px;
60 | color: #385A65;
61 | }
62 |
63 | .sender {
64 | font-weight: bold;
65 | }
66 |
67 | .sender, .text {
68 | display: inline-block;
69 | }
70 |
--------------------------------------------------------------------------------
/public/vertx-client.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011-2015 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 |
17 | var vertx = vertx || {};
18 |
19 | !function(factory) {
20 | if (typeof define === "function" && define.amd) {
21 | // Expose as an AMD module with SockJS dependency.
22 | // "vertxbus" and "sockjs" names are used because
23 | // AMD module names are derived from file names.
24 | define("vertxbus", ["sockjs"], factory);
25 | } else {
26 | // No AMD-compliant loader
27 | factory(SockJS);
28 | }
29 | }(function(SockJS) {
30 |
31 | vertx.EventBus = function(url, options) {
32 |
33 | var that = this;
34 | var sockJSConn = new SockJS(url, undefined, options);
35 | var handlerMap = {};
36 | var replyHandlers = {};
37 | var state = vertx.EventBus.CONNECTING;
38 | var pingTimerID = null;
39 | var pingInterval = null;
40 | if (options) {
41 | pingInterval = options['vertxbus_ping_interval'];
42 | }
43 | if (!pingInterval) {
44 | pingInterval = 5000;
45 | }
46 |
47 | that.onopen = null;
48 | that.onclose = null;
49 | that.onerror = null;
50 |
51 | that.send = function(address, message, replyHandler, failureHandler) {
52 | sendOrPub("send", address, message, replyHandler, failureHandler)
53 | };
54 |
55 | that.publish = function(address, message) {
56 | sendOrPub("publish", address, message, null)
57 | };
58 |
59 | that.registerHandler = function(address, handler) {
60 | checkSpecified("address", 'string', address);
61 | checkSpecified("handler", 'function', handler);
62 | checkOpen();
63 | var handlers = handlerMap[address];
64 | if (!handlers) {
65 | handlers = [handler];
66 | handlerMap[address] = handlers;
67 | // First handler for this address so we should register the connection
68 | var msg = { type : "register",
69 | address: address };
70 | sockJSConn.send(JSON.stringify(msg));
71 | } else {
72 | handlers[handlers.length] = handler;
73 | }
74 | };
75 |
76 | that.unregisterHandler = function(address, handler) {
77 | checkSpecified("address", 'string', address);
78 | checkSpecified("handler", 'function', handler);
79 | checkOpen();
80 | var handlers = handlerMap[address];
81 | if (handlers) {
82 | var idx = handlers.indexOf(handler);
83 | if (idx != -1) handlers.splice(idx, 1);
84 | if (handlers.length == 0) {
85 | // No more local handlers so we should unregister the connection
86 |
87 | var msg = { type : "unregister",
88 | address: address};
89 | sockJSConn.send(JSON.stringify(msg));
90 | delete handlerMap[address];
91 | }
92 | }
93 | };
94 |
95 | that.close = function() {
96 | checkOpen();
97 | state = vertx.EventBus.CLOSING;
98 | sockJSConn.close();
99 | };
100 |
101 | that.readyState = function() {
102 | return state;
103 | };
104 |
105 | sockJSConn.onopen = function() {
106 | // Send the first ping then send a ping every pingInterval milliseconds
107 | sendPing();
108 | pingTimerID = setInterval(sendPing, pingInterval);
109 | state = vertx.EventBus.OPEN;
110 | if (that.onopen) {
111 | that.onopen();
112 | }
113 | };
114 |
115 | sockJSConn.onclose = function() {
116 | state = vertx.EventBus.CLOSED;
117 | if (pingTimerID) clearInterval(pingTimerID);
118 | if (that.onclose) {
119 | that.onclose();
120 | }
121 | };
122 |
123 | sockJSConn.onmessage = function(e) {
124 | var msg = e.data;
125 | var json = JSON.parse(msg);
126 | var type = json.type;
127 | if (type === 'err') {
128 | if (that.onerror) {
129 | that.onerror(json.body);
130 | } else {
131 | console.error("Error received on connection: " + json.body);
132 | }
133 | return;
134 | }
135 | var body = json.body;
136 | var replyAddress = json.replyAddress;
137 | var address = json.address;
138 | var replyHandler;
139 | if (replyAddress) {
140 | replyHandler = function(reply, replyHandler) {
141 | // Send back reply
142 | that.send(replyAddress, reply, replyHandler);
143 | };
144 | }
145 | var handlers = handlerMap[address];
146 | if (handlers) {
147 | // We make a copy since the handler might get unregistered from within the
148 | // handler itself, which would screw up our iteration
149 | var copy = handlers.slice(0);
150 | for (var i = 0; i < copy.length; i++) {
151 | copy[i](body, replyHandler);
152 | }
153 | } else {
154 | // Might be a reply message
155 | handlers = replyHandlers[address];
156 | if (handlers) {
157 | delete replyHandlers[address];
158 | var handler = handlers.replyHandler;
159 | if (body) {
160 | handler(body, replyHandler);
161 | } else if (typeof json.failureCode != 'undefined') {
162 | // Check for failure
163 | var failure = { failureCode: json.failureCode, failureType: json.failureType, message: json.message };
164 | var failureHandler = handlers.failureHandler;
165 | if (failureHandler) {
166 | failureHandler(failure)
167 | }
168 | }
169 | }
170 | }
171 | };
172 |
173 | function sendPing() {
174 | var msg = {
175 | type: "ping"
176 | };
177 | sockJSConn.send(JSON.stringify(msg));
178 | }
179 |
180 | function sendOrPub(sendOrPub, address, message, replyHandler, failureHandler) {
181 | checkSpecified("address", 'string', address);
182 | checkSpecified("replyHandler", 'function', replyHandler, true);
183 | checkSpecified("failureHandler", 'function', failureHandler, true);
184 | checkOpen();
185 | var envelope = { type : sendOrPub,
186 | address: address,
187 | body: message };
188 | if (replyHandler || failureHandler) {
189 | var replyAddress = makeUUID();
190 | envelope.replyAddress = replyAddress;
191 | replyHandlers[replyAddress] = { replyHandler: replyHandler, failureHandler: failureHandler };
192 | }
193 | var str = JSON.stringify(envelope);
194 | sockJSConn.send(str);
195 | }
196 |
197 | function checkOpen() {
198 | if (state != vertx.EventBus.OPEN) {
199 | throw new Error('INVALID_STATE_ERR');
200 | }
201 | }
202 |
203 | function checkSpecified(paramName, paramType, param, optional) {
204 | if (!optional && !param) {
205 | throw new Error("Parameter " + paramName + " must be specified");
206 | }
207 | if (param && typeof param != paramType) {
208 | throw new Error("Parameter " + paramName + " must be of type " + paramType);
209 | }
210 | }
211 |
212 | function isFunction(obj) {
213 | return !!(obj && obj.constructor && obj.call && obj.apply);
214 | }
215 |
216 | function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
217 | .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})}
218 |
219 | };
220 |
221 | vertx.EventBus.CONNECTING = 0;
222 | vertx.EventBus.OPEN = 1;
223 | vertx.EventBus.CLOSING = 2;
224 | vertx.EventBus.CLOSED = 3;
225 |
226 | return vertx.EventBus;
227 |
228 | });
229 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Java Chat Demo
2 |
3 | This simple chat application demonstrates how to use the RethinkDB Java driver. The backend is written with with [Vert.x][vertx], a Java framework that is well-suited for realtime web applications. The frontend is built with [Vue.js][vue], a lightweight MVC framework that supports simple data binding.
4 |
5 | Vert.x applications are composed of microservices, each implemented in a class called a Verticle. The framework provides a built-in [event bus][eventbus] that you can use to pass messages between verticles. The Vert.x event bus also has a WebSocket bridge, implemented on top of SockJS, that you can use to propagate messages between the frontend and the backend.
6 |
7 | This application uses a RethinkDB changefeed and the Vert.x event bus to broadcast new messages to all of the connected clients over SockJS. It also exposes an HTTP POST endpoint that client applications can use to send new messages.
8 |
9 | [vertx]: http://vertx.io/
10 | [eventbus]: http://vertx.io/docs/vertx-core/java/#event_bus
11 | [vue]: http://vuejs.org/
12 |
--------------------------------------------------------------------------------
/src/main/java/com/rethinkdb/demo/RethinkDemo.java:
--------------------------------------------------------------------------------
1 | package com.rethinkdb.demo;
2 |
3 | import io.vertx.core.Vertx;
4 | import io.vertx.core.json.*;
5 | import io.vertx.core.eventbus.EventBus;
6 |
7 | import io.vertx.core.http.HttpMethod;
8 | import io.vertx.core.http.HttpServer;
9 | import io.vertx.core.http.HttpServerResponse;
10 |
11 | import io.vertx.ext.web.Router;
12 | import io.vertx.ext.web.handler.StaticHandler;
13 | import io.vertx.ext.web.handler.BodyHandler;
14 | import io.vertx.ext.web.handler.sockjs.*;
15 |
16 | import com.rethinkdb.RethinkDB;
17 | import com.rethinkdb.net.Connection;
18 | import com.rethinkdb.net.Cursor;
19 |
20 | import java.util.HashMap;
21 | import java.util.List;
22 |
23 | public class RethinkDemo {
24 | public static final RethinkDB r = RethinkDB.r;
25 | public static final String DBHOST = "rethinkdb-stable";
26 |
27 | public static void main(String[] args) {
28 | System.out.println("Attempting to start the application.");
29 |
30 | Vertx vertx = Vertx.vertx();
31 | Router router = Router.router(vertx);
32 | EventBus bus = vertx.eventBus();
33 |
34 | new Thread(() -> {
35 | Connection> conn = null;
36 |
37 | try {
38 | conn = r.connection().hostname(DBHOST).connect();
39 | Cursor cur = r.db("chat").table("messages").changes()
40 | .getField("new_val").without("time").run(conn);
41 |
42 | while (cur.hasNext())
43 | bus.publish("chat", new JsonObject(cur.next()));
44 | }
45 | catch (Exception e) {
46 | System.err.println("Error: changefeed failed");
47 | }
48 | finally {
49 | conn.close();
50 | }
51 | }).start();
52 |
53 | router.route("/eventbus/*").handler(
54 | SockJSHandler.create(vertx).bridge(
55 | new BridgeOptions().addOutboundPermitted(
56 | new PermittedOptions().setAddress("chat"))));
57 |
58 | router.route(HttpMethod.GET, "/messages").blockingHandler(ctx -> {
59 | Connection> conn = null;
60 |
61 | try {
62 | conn = r.connection().hostname(DBHOST).connect();
63 |
64 | List items = r.db("chat").table("messages")
65 | .orderBy().optArg("index", r.desc("time"))
66 | .limit(20)
67 | .orderBy("time")
68 | .run(conn);
69 |
70 | ctx.response()
71 | .putHeader("content-type", "application/json")
72 | .end(Json.encodePrettily(items));
73 | }
74 | catch (Exception e) {
75 | ctx.response()
76 | .setStatusCode(500)
77 | .putHeader("content-type", "application/json")
78 | .end("{\"success\": false}");
79 | }
80 | finally {
81 | conn.close();
82 | }
83 | });
84 |
85 | router.route(HttpMethod.POST, "/send").handler(BodyHandler.create());
86 | router.route(HttpMethod.POST, "/send").blockingHandler(ctx -> {
87 | JsonObject data = ctx.getBodyAsJson();
88 |
89 | if (data.getString("user") == null || data.getString("text") == null) {
90 | ctx.response()
91 | .setStatusCode(500)
92 | .putHeader("content-type", "application/json")
93 | .end("{\"success\": false, \"err\": \"Invalid message\"}");
94 |
95 | return;
96 | }
97 |
98 | Connection> conn = null;
99 |
100 | try {
101 | conn = r.connection().hostname(DBHOST).connect();
102 |
103 | r.db("chat").table("messages").insert(
104 | r.hashMap("text", data.getString("text"))
105 | .with("user", data.getString("user"))
106 | .with("time", r.now())).run(conn);
107 |
108 | ctx.response()
109 | .putHeader("content-type", "application/json")
110 | .end("{\"success\": true}");
111 | }
112 | catch (Exception e) {
113 | ctx.response()
114 | .setStatusCode(500)
115 | .putHeader("content-type", "application/json")
116 | .end("{\"success\": false}");
117 | }
118 | finally {
119 | conn.close();
120 | }
121 | });
122 |
123 | router.route().handler(StaticHandler.create().setWebRoot("public").setCachingEnabled(false));
124 | vertx.createHttpServer().requestHandler(router::accept).listen(8000);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------