├── common
├── build
│ ├── tmp
│ │ └── jar
│ │ │ └── MANIFEST.MF
│ └── resources
│ │ └── main
│ │ └── log4j2.xml
├── src
│ └── main
│ │ ├── java
│ │ ├── routerutils
│ │ │ ├── Middleware.java
│ │ │ ├── RouteConfig.java
│ │ │ ├── BaseHandler.java
│ │ │ ├── RouteBuilder.java
│ │ │ └── HandlerProcessor.java
│ │ └── logging
│ │ │ └── ContextLogger.java
│ │ └── resources
│ │ └── log4j2.xml
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── conduit
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── src
│ ├── main
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── vertx
│ │ │ │ └── conduit
│ │ │ │ ├── entities
│ │ │ │ ├── package-info.java
│ │ │ │ ├── Comment.java
│ │ │ │ ├── Base.java
│ │ │ │ ├── Article.java
│ │ │ │ └── User.java
│ │ │ │ ├── services
│ │ │ │ ├── package-info.java
│ │ │ │ ├── MorphiaServiceOperator.java
│ │ │ │ ├── ArticleService.java
│ │ │ │ ├── CommentService.java
│ │ │ │ ├── UserService.java
│ │ │ │ ├── MongoDbService.java
│ │ │ │ ├── MorphiaService.java
│ │ │ │ ├── CommentServiceImpl.java
│ │ │ │ ├── ArticleServiceImpl.java
│ │ │ │ ├── UserServiceImpl.java
│ │ │ │ ├── MongoDbServiceImpl.java
│ │ │ │ └── MorphiaServiceImpl.java
│ │ │ │ ├── handlers
│ │ │ │ ├── Constants.java
│ │ │ │ ├── QueryHandler.java
│ │ │ │ ├── JwtOptionalHandler.java
│ │ │ │ ├── ConduitJwtAuthHandlerImpl.java
│ │ │ │ ├── ConduitHandler.java
│ │ │ │ ├── UserHandler.java
│ │ │ │ └── ArticleHandler.java
│ │ │ │ ├── verticles
│ │ │ │ ├── UserServiceVerticle.java
│ │ │ │ ├── ArticleServiceVerticle.java
│ │ │ │ ├── CommentServiceVerticle.java
│ │ │ │ ├── HttpVerticle.java
│ │ │ │ ├── MorphiaServiceVerticle.java
│ │ │ │ └── MongoDbServiceVerticle.java
│ │ │ │ └── App.java
│ │ └── resources
│ │ │ └── app.json
│ └── test
│ │ └── java
│ │ ├── services
│ │ ├── MongoServiceTest.java
│ │ └── MorphiaServiceTest.java
│ │ └── routes
│ │ ├── QueryTest.java
│ │ ├── UserTest.java
│ │ ├── ArticleTest.java
│ │ └── TestBase.java
├── gradlew.bat
├── build.gradle
└── gradlew
├── .idea
├── vcs.xml
├── misc.xml
├── gradle.xml
└── uiDesigner.xml
├── .gitignore
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/common/build/tmp/jar/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 |
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'vertx-examples'
2 |
3 | include ":common", ":conduit"
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndierZ/conduit/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/conduit/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AndierZ/conduit/HEAD/conduit/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/entities/package-info.java:
--------------------------------------------------------------------------------
1 | @ModuleGen(name = "realworld-vertx-entities", groupPackage = "io.vertx.conduit.entities")
2 | package io.vertx.conduit.entities;
3 |
4 | import io.vertx.codegen.annotations.ModuleGen;
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/package-info.java:
--------------------------------------------------------------------------------
1 | @ModuleGen(name = "realworld-vertx-entities", groupPackage = "io.vertx.conduit.services")
2 | package io.vertx.conduit.services;
3 |
4 | import io.vertx.codegen.annotations.ModuleGen;
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/conduit/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/MorphiaServiceOperator.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | public class MorphiaServiceOperator {
4 |
5 | public final static String PUSH = "$push";
6 | public final static String POP = "$pop";
7 | }
8 |
--------------------------------------------------------------------------------
/conduit/src/main/resources/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": "test",
3 | "secret" : "1summerday20!9",
4 |
5 | "mongodb": {
6 | "db_name": "conduit",
7 | "useObjectId": true,
8 | "connection_string": "mongodb://localhost:27017",
9 | "host": "localhost",
10 | "port": 27017
11 | }
12 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/common/src/main/java/routerutils/Middleware.java:
--------------------------------------------------------------------------------
1 | package routerutils;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @SuppressWarnings("unused")
9 | @Target({ ElementType.METHOD })
10 | @Retention(RetentionPolicy.SOURCE)
11 | public @interface Middleware {
12 | }
13 |
--------------------------------------------------------------------------------
/common/src/main/java/logging/ContextLogger.java:
--------------------------------------------------------------------------------
1 | package logging;
2 |
3 | import io.vertx.core.logging.Logger;
4 | import io.vertx.core.logging.LoggerFactory;
5 |
6 | import java.lang.management.ManagementFactory;
7 |
8 | public class ContextLogger {
9 |
10 | public static Logger create() {
11 | StackTraceElement[] stackTrade = ManagementFactory.getThreadMXBean().getThreadInfo(Thread.currentThread().getId(), 5).getStackTrace();
12 | String clz = stackTrade[4].getClassName();
13 | return LoggerFactory.getLogger(clz);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/Constants.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | public class Constants {
4 | public static final String ARTICLE = "article";
5 | public static final String COMMENT = "comment";
6 | public static final String AUTH_KEY = "token"; // send in the response
7 | public static final String AUTH_HEADER = "Token"; // received in the authorization header
8 | public static final String USER = "user";
9 | public static final String USER_ID = "userId";
10 | public static final String QUERY = "query";
11 | }
12 |
--------------------------------------------------------------------------------
/common/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | version '1.0-SNAPSHOT'
6 |
7 | sourceCompatibility = 1.8
8 |
9 | repositories {
10 | jcenter()
11 | }
12 |
13 | dependencies {
14 | compile group: 'io.vertx', name:'vertx-core', version: '3.8.4'
15 | compile group: 'io.vertx', name: 'vertx-web', version: '3.8.4'
16 | compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.2'
17 | compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.2'
18 | testCompile group: 'junit', name: 'junit', version: '4.12'
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | # .gradle files
26 | .gradle/
27 | !gradle-wrapper.jar
28 |
29 | # build files
30 | build/
31 | common/buid/
32 | conduit/build/
33 |
34 | # intellij project file
35 | .idea/workspace.xml
36 |
37 | # generated code
38 | conduit/src/main/generated
--------------------------------------------------------------------------------
/common/src/main/java/routerutils/RouteConfig.java:
--------------------------------------------------------------------------------
1 | package routerutils;
2 |
3 | import io.vertx.core.http.HttpMethod;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | @Target({ ElementType.METHOD, ElementType.TYPE })
11 | @Retention(RetentionPolicy.RUNTIME)
12 | public @interface RouteConfig {
13 | String path();
14 |
15 | HttpMethod method() default HttpMethod.GET;
16 |
17 | String[] consumes() default {};
18 |
19 | String[] produces() default {};
20 |
21 | boolean authRequired() default true;
22 |
23 | String[] middlewares() default {};
24 | }
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/ArticleService.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.codegen.annotations.ProxyGen;
4 | import io.vertx.codegen.annotations.VertxGen;
5 | import io.vertx.conduit.entities.Article;
6 | import io.vertx.core.AsyncResult;
7 | import io.vertx.core.Handler;
8 | import io.vertx.core.json.JsonObject;
9 |
10 | import java.util.List;
11 |
12 | @VertxGen
13 | @ProxyGen
14 | public interface ArticleService {
15 |
16 | String ADDRESS = ArticleService.class.getName();
17 |
18 | void create(JsonObject article, Handler> resultHandler);
19 |
20 | void update(String slug, JsonObject update, Handler> resultHandler);
21 |
22 | void get(String slug, Handler> resultHandler);
23 |
24 | void delete(String slug, Handler> resultHandler);
25 | }
26 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/CommentService.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.codegen.annotations.ProxyGen;
4 | import io.vertx.codegen.annotations.VertxGen;
5 | import io.vertx.conduit.entities.Article;
6 | import io.vertx.conduit.entities.Comment;
7 | import io.vertx.core.AsyncResult;
8 | import io.vertx.core.Handler;
9 | import io.vertx.core.json.JsonObject;
10 |
11 | @VertxGen
12 | @ProxyGen
13 | public interface CommentService {
14 |
15 | String ADDRESS = CommentService.class.getName();
16 |
17 | void create(JsonObject comment, Handler> resultHandler);
18 |
19 | void update(String id, JsonObject update, Handler> resultHandler);
20 |
21 | void get(String id, Handler> resultHandler);
22 |
23 | void delete(String id, Handler> resultHandler);
24 | }
25 |
--------------------------------------------------------------------------------
/common/src/main/java/routerutils/BaseHandler.java:
--------------------------------------------------------------------------------
1 | package routerutils;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.core.Vertx;
5 | import io.vertx.core.json.Json;
6 | import io.vertx.core.json.JsonObject;
7 | import io.vertx.ext.web.RoutingContext;
8 |
9 | import javax.xml.ws.http.HTTPBinding;
10 |
11 | public abstract class BaseHandler {
12 |
13 | protected final Vertx vertx;
14 |
15 | protected BaseHandler(Vertx vertx) {
16 | this.vertx = vertx;
17 | }
18 |
19 | protected static void handleError(RoutingContext event, Throwable ex) {
20 | if (ex != null) {
21 | event.fail(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), ex);
22 | }
23 | }
24 |
25 | protected static void handleResponse(RoutingContext event, JsonObject res, HttpResponseStatus successStatus) {
26 | event.response()
27 | .setStatusCode(successStatus.code())
28 | .end(Json.encodePrettily(res));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/common/build/resources/main/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/common/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/UserService.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.codegen.annotations.ProxyGen;
4 | import io.vertx.codegen.annotations.VertxGen;
5 | import io.vertx.conduit.entities.User;
6 | import io.vertx.core.AsyncResult;
7 | import io.vertx.core.Handler;
8 | import io.vertx.core.json.JsonObject;
9 |
10 | @VertxGen
11 | @ProxyGen
12 | public interface UserService {
13 | String ADDRESS = UserService.class.getName();
14 |
15 | void create(JsonObject create, Handler> resultHandler);
16 |
17 | void get(JsonObject query, Handler> resultHandler);
18 |
19 | void getById(String id, Handler> resultHandler);
20 |
21 | void getByEmail(String email, Handler> resultHandler);
22 |
23 | void update(String id, JsonObject update, Handler> resultHandler);
24 |
25 | void getFavoriteCount(String slug, Handler> resultHandler);
26 |
27 | void deleteByUsername(String username, Handler> resultHandler);
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 AndierZ
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/QueryHandler.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.codegen.annotations.Nullable;
5 | import io.vertx.conduit.entities.User;
6 | import io.vertx.core.Vertx;
7 | import io.vertx.core.http.HttpMethod;
8 | import io.vertx.core.json.Json;
9 | import io.vertx.core.json.JsonArray;
10 | import io.vertx.core.json.JsonObject;
11 | import io.vertx.ext.web.RoutingContext;
12 | import routerutils.RouteConfig;
13 |
14 | @RouteConfig(path="/api", produces = "application/json")
15 | public class QueryHandler extends ConduitHandler {
16 |
17 | private static final String QUERY = "query";
18 |
19 | public QueryHandler(Vertx vertx) {
20 | super(vertx);
21 | }
22 |
23 | @RouteConfig(path="/tags", method= HttpMethod.GET, authRequired = false)
24 | public void getTags(RoutingContext event){
25 | morphiaService.rxQueryTags()
26 | .subscribe((tags, ex) -> {
27 | if (ex == null) {
28 | JsonArray array = new JsonArray();
29 | tags.forEach(array::add);
30 | event.response()
31 | .setStatusCode(HttpResponseStatus.OK.code())
32 | .end(Json.encodePrettily(new JsonObject().put("tags", array)));
33 | } else {
34 | event.fail(ex);
35 | }
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/common/src/main/java/routerutils/RouteBuilder.java:
--------------------------------------------------------------------------------
1 | package routerutils;
2 |
3 | import io.vertx.core.Handler;
4 | import io.vertx.core.Vertx;
5 | import io.vertx.ext.web.Router;
6 | import io.vertx.ext.web.RoutingContext;
7 | import io.vertx.ext.web.handler.AuthHandler;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | public class RouteBuilder {
13 |
14 | private boolean finalized;
15 | private final List handlers;
16 | private final Router baseRouter;
17 | private final List> preHandlers;
18 |
19 | private AuthHandler authHandler;
20 | private Handler optionalAuthHandler;
21 |
22 | public RouteBuilder(Router baseRouter) {
23 | this.handlers = new ArrayList<>();
24 | this.preHandlers = new ArrayList<>();
25 | this.baseRouter = baseRouter;
26 | }
27 |
28 | public RouteBuilder addAuthHandler(AuthHandler authHandler) {
29 | if (finalized) {
30 | throw new RuntimeException("Routes already built.");
31 | }
32 | this.authHandler = authHandler;
33 | return this;
34 | }
35 |
36 | public RouteBuilder addPreHandler(Handler preHandler) {
37 | if (finalized) {
38 | throw new RuntimeException("Routes already built.");
39 | }
40 | this.preHandlers.add(preHandler);
41 | return this;
42 | }
43 |
44 | public RouteBuilder add(BaseHandler handler){
45 | if (finalized) {
46 | throw new RuntimeException("Routes already built.");
47 | }
48 |
49 | handlers.add(handler);
50 |
51 | return this;
52 | }
53 |
54 | public void build() {
55 | if (finalized) {
56 | throw new RuntimeException("Routes already built.");
57 | }
58 |
59 | for(BaseHandler handler : handlers) {
60 | HandlerProcessor.buildHandler(baseRouter, preHandlers, handler, authHandler);
61 | }
62 |
63 | finalized = true;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/MongoDbService.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.codegen.annotations.Fluent;
4 | import io.vertx.codegen.annotations.ProxyClose;
5 | import io.vertx.codegen.annotations.ProxyGen;
6 | import io.vertx.codegen.annotations.VertxGen;
7 | import io.vertx.core.AsyncResult;
8 | import io.vertx.core.Handler;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.ext.mongo.FindOptions;
11 | import io.vertx.ext.mongo.MongoClientDeleteResult;
12 | import io.vertx.ext.mongo.MongoClientUpdateResult;
13 | import io.vertx.ext.mongo.UpdateOptions;
14 |
15 | import java.util.List;
16 |
17 | @VertxGen
18 | @ProxyGen
19 | public interface MongoDbService {
20 |
21 | String ADDRESS = MongoDbService.class.getName();
22 |
23 | void findOne(final String collection, final JsonObject query, final JsonObject fields, final Handler> resultHandler);
24 |
25 | void findById(final String collection, String id, final JsonObject fields, final Handler> resultHandler);
26 |
27 | void find(final String collection, final JsonObject query, final FindOptions options, Handler>> resultHandler);
28 |
29 | void insertOne(final String collection, final JsonObject document, final Handler> resultHandler);
30 |
31 | void upsert(final String collection, final JsonObject query, final JsonObject update, final UpdateOptions options, Handler> resultHandler);
32 |
33 | void findOneAndUpdate(final String collection, final JsonObject query, final JsonObject update, final FindOptions findOptions, final UpdateOptions updateOptions, final Handler> resultHandler);
34 |
35 | void findOneAndReplace(String collection, JsonObject query, JsonObject update, FindOptions findOptions, UpdateOptions updateOptions, Handler> resultHandler);
36 |
37 | void delete(final String collection, final JsonObject query, Handler> resultHandler);
38 |
39 | @ProxyClose
40 | void close();
41 | }
42 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/UserServiceVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.handlers.Constants;
4 | import io.vertx.core.Promise;
5 | import logging.ContextLogger;
6 | import io.vertx.conduit.services.UserService;
7 | import io.vertx.conduit.services.UserServiceImpl;
8 | import io.vertx.core.AbstractVerticle;
9 | import io.vertx.core.eventbus.MessageConsumer;
10 | import io.vertx.core.json.JsonObject;
11 | import io.vertx.core.logging.Logger;
12 | import io.vertx.servicediscovery.Record;
13 | import io.vertx.servicediscovery.ServiceDiscovery;
14 | import io.vertx.servicediscovery.types.EventBusService;
15 | import io.vertx.serviceproxy.ServiceBinder;
16 |
17 | public class UserServiceVerticle extends AbstractVerticle {
18 |
19 | private static Logger LOGGER = ContextLogger.create();
20 |
21 | private Record record;
22 | private ServiceBinder binder;
23 | private MessageConsumer consumer;
24 |
25 | @Override
26 | public void start(Promise startPromise) {
27 |
28 | ServiceDiscovery.create(vertx, discovery -> {
29 | binder = new ServiceBinder(vertx);
30 | // Create the services object
31 | UserServiceImpl service = new UserServiceImpl(vertx);
32 |
33 | // Register the services proxy on the event bus
34 | this.consumer = binder
35 | .setAddress(UserService.ADDRESS)
36 | .register(UserService.class, service);
37 |
38 | Record record = EventBusService.createRecord(Constants.USER, UserService.ADDRESS, UserService.class.getName());
39 | discovery.publish(record, ar -> {
40 | if (ar.succeeded()) {
41 | this.record = record;
42 | LOGGER.info("User service published");
43 | startPromise.complete();
44 | } else {
45 | LOGGER.error("Error publishing user service", ar.cause());
46 | startPromise.fail(ar.cause());
47 | }
48 | });
49 |
50 | });
51 | }
52 |
53 | @Override
54 | public void stop() {
55 | if (record != null) {
56 | binder.unregister(consumer);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/entities/Comment.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.entities;
2 |
3 | import dev.morphia.annotations.Entity;
4 | import dev.morphia.annotations.Reference;
5 | import io.vertx.codegen.annotations.DataObject;
6 | import io.vertx.core.json.JsonObject;
7 | import org.bson.types.ObjectId;
8 |
9 | import javax.validation.constraints.NotNull;
10 | import java.util.Date;
11 |
12 | @DataObject(generateConverter = true)
13 | @Entity("comments")
14 | public class Comment extends Base {
15 |
16 | public Comment() {}
17 |
18 | public Comment(JsonObject jsonObject) {
19 | fromJson(jsonObject);
20 | }
21 |
22 | private String body;
23 |
24 | @NotNull
25 | @Reference(idOnly = true, lazy = true)
26 | private User author;
27 |
28 | // Not using reference to avoid circular dependency; also not necessary.
29 | @NotNull
30 | private ObjectId article;
31 |
32 | public String getBody() {
33 | return body;
34 | }
35 |
36 | public void setBody(String body) {
37 | this.body = body;
38 | }
39 |
40 | public User getAuthor() {
41 | return author;
42 | }
43 |
44 | public void setAuthor(User author) {
45 | this.author = author;
46 | }
47 |
48 | public ObjectId getArticle() {
49 | return article;
50 | }
51 |
52 | public void setArticle(ObjectId article) {
53 | this.article = article;
54 | }
55 |
56 | public JsonObject toJson() {
57 | JsonObject json = new JsonObject();
58 | CommentConverter.toJson(this, json);
59 | super.toJson(json);
60 | json.put("article", article != null ? article.toHexString() : null);
61 | return json;
62 | }
63 |
64 | protected void fromJson(JsonObject jsonObject) {
65 | super.fromJson(jsonObject);
66 | CommentConverter.fromJson(jsonObject, this);
67 | this.article = new ObjectId(jsonObject.getString("article"));
68 | }
69 |
70 | public JsonObject toJsonFor(User user) {
71 | JsonObject json = toJson();
72 | json.put("author", author.toProfileJsonFor(user));
73 | json.put("createdAt", DF.format(new Date(getCreatedAt())));
74 | json.put("updatedAt", DF.format(new Date(getCreatedAt())));
75 | return json;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/ArticleServiceVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.handlers.Constants;
4 | import io.vertx.conduit.services.ArticleService;
5 | import io.vertx.conduit.services.ArticleServiceImpl;
6 | import io.vertx.core.AbstractVerticle;
7 | import io.vertx.core.Promise;
8 | import io.vertx.core.eventbus.MessageConsumer;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.core.logging.Logger;
11 | import io.vertx.servicediscovery.Record;
12 | import io.vertx.servicediscovery.ServiceDiscovery;
13 | import io.vertx.servicediscovery.types.EventBusService;
14 | import io.vertx.serviceproxy.ServiceBinder;
15 | import logging.ContextLogger;
16 |
17 | public class ArticleServiceVerticle extends AbstractVerticle {
18 |
19 | private static Logger LOGGER = ContextLogger.create();
20 |
21 | private Record record;
22 | private ServiceBinder binder;
23 | private MessageConsumer consumer;
24 |
25 | @Override
26 | public void start(Promise startPromise) {
27 |
28 | ServiceDiscovery.create(vertx, discovery -> {
29 | binder = new ServiceBinder(vertx);
30 | // Create the services object
31 | ArticleServiceImpl service = new ArticleServiceImpl(vertx);
32 |
33 | // Register the services proxy on the event bus
34 | this.consumer = binder
35 | .setAddress(ArticleService.ADDRESS)
36 | .register(ArticleService.class, service);
37 |
38 | Record record = EventBusService.createRecord(Constants.ARTICLE, ArticleService.ADDRESS, ArticleService.class.getName());
39 | discovery.publish(record, ar -> {
40 | if (ar.succeeded()) {
41 | this.record = record;
42 | LOGGER.info("Article service published");
43 | startPromise.complete();
44 | } else {
45 | LOGGER.error("Error publishing article service", ar.cause());
46 | startPromise.fail(ar.cause());
47 | }
48 | });
49 |
50 | });
51 | }
52 |
53 | @Override
54 | public void stop() {
55 | if (record != null) {
56 | binder.unregister(consumer);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/CommentServiceVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.handlers.Constants;
4 | import io.vertx.conduit.services.CommentService;
5 | import io.vertx.conduit.services.CommentServiceImpl;
6 | import io.vertx.core.AbstractVerticle;
7 | import io.vertx.core.Promise;
8 | import io.vertx.core.eventbus.MessageConsumer;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.core.logging.Logger;
11 | import io.vertx.servicediscovery.Record;
12 | import io.vertx.servicediscovery.ServiceDiscovery;
13 | import io.vertx.servicediscovery.types.EventBusService;
14 | import io.vertx.serviceproxy.ServiceBinder;
15 | import logging.ContextLogger;
16 |
17 | public class CommentServiceVerticle extends AbstractVerticle {
18 |
19 | private static Logger LOGGER = ContextLogger.create();
20 |
21 | private Record record;
22 | private ServiceBinder binder;
23 | private MessageConsumer consumer;
24 |
25 | @Override
26 | public void start(Promise startPromise) {
27 |
28 | ServiceDiscovery.create(vertx, discovery -> {
29 | binder = new ServiceBinder(vertx);
30 | // Create the services object
31 | CommentServiceImpl service = new CommentServiceImpl(vertx);
32 |
33 | // Register the services proxy on the event bus
34 | this.consumer = binder
35 | .setAddress(CommentService.ADDRESS)
36 | .register(CommentService.class, service);
37 |
38 | Record record = EventBusService.createRecord(Constants.COMMENT, CommentService.ADDRESS, CommentService.class.getName());
39 | discovery.publish(record, ar -> {
40 | if (ar.succeeded()) {
41 | this.record = record;
42 | LOGGER.info("Comment service published");
43 | startPromise.complete();
44 | } else {
45 | LOGGER.error("Error publishing comment service", ar.cause());
46 | startPromise.fail(ar.cause());
47 | }
48 | });
49 |
50 | });
51 | }
52 |
53 | @Override
54 | public void stop() {
55 | if (record != null) {
56 | binder.unregister(consumer);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/HttpVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.handlers.*;
4 | import io.vertx.core.Promise;
5 | import logging.ContextLogger;
6 | import io.vertx.core.AbstractVerticle;
7 | import io.vertx.core.json.JsonObject;
8 | import io.vertx.core.logging.Logger;
9 | import io.vertx.ext.auth.PubSecKeyOptions;
10 | import io.vertx.ext.auth.jwt.JWTAuth;
11 | import io.vertx.ext.auth.jwt.JWTAuthOptions;
12 | import io.vertx.ext.web.Router;
13 | import io.vertx.ext.web.handler.BodyHandler;
14 | import routerutils.RouteBuilder;
15 |
16 | public class HttpVerticle extends AbstractVerticle {
17 |
18 | private static Logger LOGGER = ContextLogger.create();
19 | private JWTAuth jwtAuth;
20 |
21 | @Override
22 | public void start(Promise startPromise) {
23 | LOGGER.info("Starting Http Verticle");
24 |
25 | JsonObject config = config();
26 |
27 | JWTAuthOptions jwtAuthOptions = new JWTAuthOptions().addPubSecKey(
28 | new PubSecKeyOptions()
29 | .setAlgorithm("HS256")
30 | .setPublicKey(config.getString("secret"))
31 | .setSymmetric(true));
32 |
33 | this.jwtAuth = JWTAuth.create(vertx, jwtAuthOptions);
34 |
35 | Router baseRouter = Router.router(vertx);
36 |
37 | new RouteBuilder(baseRouter)
38 | .addAuthHandler(new ConduitJwtAuthHandlerImpl(jwtAuth, Constants.AUTH_HEADER)) // To expect the header value defined by the Conduit api "token". Otherwise could just use JWTAuthHandler.create(jwtAuth)
39 | .addPreHandler(BodyHandler.create())
40 | .addPreHandler(new JwtOptionalHandler(jwtAuthOptions))
41 | .add(new UserHandler(vertx, jwtAuth))
42 | .add(new ArticleHandler(vertx))
43 | .add(new QueryHandler(vertx))
44 | .build();
45 |
46 | vertx.createHttpServer()
47 | .requestHandler(baseRouter)
48 | .listen(3000, ar -> {
49 | if (ar.succeeded()) {
50 | startPromise.complete();
51 | } else {
52 | startPromise.fail(ar.cause());
53 | }
54 | });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/JwtOptionalHandler.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import io.vertx.core.Handler;
4 | import io.vertx.core.http.HttpHeaders;
5 | import io.vertx.core.json.JsonObject;
6 | import io.vertx.ext.auth.PubSecKeyOptions;
7 | import io.vertx.ext.auth.jwt.JWTAuthOptions;
8 | import io.vertx.ext.auth.jwt.impl.JWTUser;
9 | import io.vertx.ext.jwt.JWK;
10 | import io.vertx.ext.jwt.JWT;
11 | import io.vertx.ext.web.RoutingContext;
12 |
13 | import java.util.List;
14 |
15 | import static io.vertx.conduit.handlers.Constants.USER_ID;
16 |
17 | public class JwtOptionalHandler implements Handler {
18 |
19 | private final JWT jwt;
20 | private final String permissionsClaimKey;
21 |
22 | public JwtOptionalHandler(JWTAuthOptions config) {
23 | this.jwt = new JWT();
24 | this.permissionsClaimKey = config.getPermissionsClaimKey();
25 |
26 | final List keys = config.getPubSecKeys();
27 |
28 | if (keys != null) {
29 | for (PubSecKeyOptions pubSecKey : config.getPubSecKeys()) {
30 | if (pubSecKey.isSymmetric()) {
31 | jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.getPublicKey()));
32 | } else {
33 | jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.isCertificate(), pubSecKey.getPublicKey(), pubSecKey.getSecretKey()));
34 | }
35 | }
36 | }
37 | }
38 |
39 | @Override
40 | public void handle(RoutingContext event) {
41 | if (event.user() == null) {
42 | final String authorization = event.request().headers().get(HttpHeaders.AUTHORIZATION);
43 | if (authorization != null) {
44 | String[] tokens = authorization.split(" ");
45 | if (tokens.length == 2) {
46 | JsonObject payload = jwt.decode(tokens[1]);
47 | event.setUser(new JWTUser(payload, permissionsClaimKey));
48 | }
49 | }
50 | }
51 |
52 | if (event.user() != null && event.user().principal() != null) {
53 | event.put(USER_ID, event.user().principal().getString("id"));
54 | } else {
55 | event.put(USER_ID, null);
56 | }
57 |
58 | event.next();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/MorphiaService.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.codegen.annotations.ProxyClose;
4 | import io.vertx.codegen.annotations.ProxyGen;
5 | import io.vertx.codegen.annotations.VertxGen;
6 | import io.vertx.conduit.entities.Article;
7 | import io.vertx.conduit.entities.Comment;
8 | import io.vertx.conduit.entities.User;
9 | import io.vertx.core.AsyncResult;
10 | import io.vertx.core.Handler;
11 | import io.vertx.core.json.JsonObject;
12 | import io.vertx.ext.mongo.FindOptions;
13 | import io.vertx.ext.mongo.MongoClientDeleteResult;
14 | import io.vertx.ext.mongo.MongoClientUpdateResult;
15 | import io.vertx.ext.mongo.UpdateOptions;
16 |
17 | import java.util.List;
18 |
19 | @VertxGen
20 | @ProxyGen
21 | public interface MorphiaService {
22 |
23 | String ADDRESS = MorphiaService.class.getName();
24 |
25 | void getUser(final JsonObject query, Handler>> resultHandler);
26 |
27 | void getArticle(final JsonObject query, Handler>> resultHandler);
28 |
29 | void getComment(final JsonObject query, Handler>> resultHandler);
30 |
31 | void createUser(User entity, final Handler> resultHandler);
32 |
33 | void createArticle(Article entity, final Handler> resultHandler);
34 |
35 | void createComment(Comment entity, final Handler> resultHandler);
36 |
37 | void updateUser(final JsonObject query, final JsonObject update, final Handler>> resultHandler);
38 |
39 | void updateArticle(final JsonObject query, final JsonObject update, final Handler>> resultHandler);
40 |
41 | void updateComment(final JsonObject query, final JsonObject update, final Handler>> resultHandler);
42 |
43 | void deleteUser(final JsonObject query, Handler> resultHandler);
44 |
45 | void deleteArticle(final JsonObject query, Handler> resultHandler);
46 |
47 | void deleteComment(final JsonObject query, Handler> resultHandler);
48 |
49 | void queryTags(Handler>> resultHandler);
50 |
51 | void queryArticles(User user, JsonObject query, Handler> resultHandler);
52 |
53 | void queryArticlesFeed(User user, JsonObject json, Handler> resultHandler);
54 |
55 | @ProxyClose
56 | void close();
57 | }
58 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/MorphiaServiceVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.services.MorphiaService;
4 | import io.vertx.conduit.services.MorphiaServiceImpl;
5 | import io.vertx.core.AbstractVerticle;
6 | import io.vertx.core.Future;
7 | import io.vertx.core.Promise;
8 | import io.vertx.core.eventbus.MessageConsumer;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.core.logging.Logger;
11 | import io.vertx.servicediscovery.Record;
12 | import io.vertx.servicediscovery.ServiceDiscovery;
13 | import io.vertx.servicediscovery.types.EventBusService;
14 | import io.vertx.serviceproxy.ServiceBinder;
15 | import logging.ContextLogger;
16 |
17 | public class MorphiaServiceVerticle extends AbstractVerticle {
18 |
19 | private static Logger LOGGER = ContextLogger.create();
20 |
21 | private Record record;
22 | private ServiceBinder binder;
23 | private MessageConsumer consumer;
24 |
25 | @Override
26 | public void start(Promise startPromise) {
27 |
28 | ServiceDiscovery.create(vertx, discovery -> {
29 | binder = new ServiceBinder(vertx);
30 | // Create the services object
31 | new MorphiaServiceImpl(vertx, config().getJsonObject("mongodb"), ready -> {
32 | if (ready.succeeded()) {
33 | this.consumer = binder
34 | .setAddress(MorphiaService.ADDRESS)
35 | .register(MorphiaService.class, ready.result());
36 | Record record = EventBusService.createRecord("morphia", MorphiaService.ADDRESS, MorphiaService.class.getName());
37 | discovery.publish(record, ar -> {
38 | if (ar.succeeded()) {
39 | this.record = record;
40 | LOGGER.info("Morphia service published");
41 | startPromise.complete();
42 | } else {
43 | LOGGER.error("Error publishing Morphia service", ar.cause());
44 | startPromise.fail(ar.cause());
45 | }
46 | });
47 | } else {
48 | startPromise.fail(ready.cause());
49 | }
50 | });
51 | });
52 |
53 |
54 | }
55 |
56 | @Override
57 | public void stop() {
58 | if (record != null) {
59 | binder.unregister(consumer);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS="-Xmx64m"
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/conduit/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS="-Xmx64m"
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/verticles/MongoDbServiceVerticle.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.verticles;
2 |
3 | import io.vertx.conduit.services.MongoDbService;
4 | import io.vertx.conduit.services.MongoDbServiceImpl;
5 | import io.vertx.core.Future;
6 | import io.vertx.core.Promise;
7 | import io.vertx.core.eventbus.MessageConsumer;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.core.logging.Logger;
10 | import io.vertx.reactivex.core.AbstractVerticle;
11 | import io.vertx.reactivex.ext.mongo.MongoClient;
12 | import io.vertx.servicediscovery.Record;
13 | import io.vertx.servicediscovery.ServiceDiscovery;
14 | import io.vertx.servicediscovery.types.EventBusService;
15 | import io.vertx.serviceproxy.ServiceBinder;
16 | import logging.ContextLogger;
17 |
18 | public class MongoDbServiceVerticle extends AbstractVerticle {
19 |
20 | private static Logger LOGGER = ContextLogger.create();
21 |
22 | private Record record;
23 | private ServiceBinder binder;
24 | private MessageConsumer consumer;
25 |
26 | @Override
27 | public void start(Promise startPromise) {
28 |
29 | final MongoClient mongoClient = MongoClient.createShared(vertx, config().getJsonObject("mongodb"));
30 | ServiceDiscovery.create(vertx.getDelegate(), discovery -> {
31 | binder = new ServiceBinder(vertx.getDelegate());
32 | // Create the services object
33 | new MongoDbServiceImpl(mongoClient, ready -> {
34 | if (ready.succeeded()) {
35 | this.consumer = binder
36 | .setAddress(MongoDbService.ADDRESS)
37 | .register(MongoDbService.class, ready.result());
38 | Record record = EventBusService.createRecord("mongodb", MongoDbService.ADDRESS, MongoDbService.class.getName());
39 | discovery.publish(record, ar -> {
40 | if (ar.succeeded()) {
41 | this.record = record;
42 | LOGGER.info("MongoDb service published");
43 | startPromise.complete();
44 | } else {
45 | LOGGER.error("Error publishing MongoDb service", ar.cause());
46 | startPromise.fail(ready.cause());
47 | }
48 | });
49 | } else {
50 | startPromise.fail(ready.cause());
51 | }
52 | });
53 | });
54 |
55 |
56 | }
57 |
58 | @Override
59 | public void stop() {
60 | if (record != null) {
61 | binder.unregister(consumer);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/conduit/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'io.vertx.vertx-plugin' version '1.0.1'
3 |
4 | }
5 |
6 | repositories {
7 | jcenter()
8 | mavenCentral()
9 | }
10 |
11 | vertx {
12 | mainVerticle = 'io.vertx.conduit.App'
13 | vertxVersion = "3.8.4"
14 | jvmArgs = ["-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.Log4j2LogDelegateFactory"]
15 | debugSuspend = true
16 | debugPort = 5005
17 | }
18 |
19 | dependencies{
20 | compile group: 'io.vertx', name: 'vertx-config'
21 | compile group: 'io.vertx', name: 'vertx-web'
22 | compile group: 'io.vertx', name: 'vertx-mongo-client'
23 | compile group: 'io.vertx', name: 'vertx-auth-jwt'
24 | compile group: 'io.vertx', name: 'vertx-service-proxy'
25 | compile group: 'io.vertx', name: 'vertx-service-discovery'
26 | compile group: 'io.vertx', name: 'vertx-rx-java2'
27 | compile group: 'io.vertx', name: 'vertx-rx-java2-gen', version: '3.8.4'
28 | compile group: 'io.vertx', name: 'vertx-web-client'
29 | compile group: 'com.github.slugify', name: 'slugify', version: '2.2'
30 | compile group: 'org.springframework.security', name: 'spring-security-crypto', version: '3.1.0.RELEASE'
31 | compile group: 'dev.morphia.morphia', name: 'core', version: '1.5.8'
32 | compile group: 'dev.morphia.morphia', name: 'validation', version: '1.5.8'
33 | compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
34 | compile group: 'org.hibernate', name: 'hibernate-validator', version: '6.1.0.Final'
35 | compile group: 'javax.el', name: 'javax.el-api', version: '3.0.0'
36 | compile group: 'org.glassfish', name: 'javax.el', version: '3.0.0'
37 | compileOnly group: 'io.vertx', name: 'vertx-codegen'
38 | compile project (':common')
39 |
40 | testCompile group: 'io.vertx', name:'vertx-unit'
41 | testCompile group: 'junit', name:'junit', version:'4.12'
42 | }
43 |
44 | def generated_dir = 'src/main/generated'
45 |
46 | task cleanGenerate(type: Delete, group: 'build') {
47 | delete project.file(generated_dir)
48 | }
49 |
50 | task generate(type: JavaCompile, group: 'build') {
51 | source = sourceSets.main.java
52 | classpath = configurations.compile + configurations.compileOnly
53 | destinationDir = project.file(generated_dir)
54 | options.annotationProcessorPath = configurations.compile + configurations.compileOnly
55 | options.compilerArgs = [
56 | "-proc:only",
57 | "-processor", "io.vertx.codegen.CodeGenProcessor"
58 | ]
59 | }
60 |
61 | sourceSets {
62 | main {
63 | java {
64 | srcDirs += generated_dir
65 | }
66 | }
67 | test {
68 | java {
69 | srcDirs 'test/java'
70 | }
71 | }
72 | }
73 |
74 | compileJava {
75 | targetCompatibility = 1.8
76 | sourceCompatibility = 1.8
77 |
78 | dependsOn generate
79 | }
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/App.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit;
2 |
3 | import io.vertx.conduit.verticles.*;
4 | import io.vertx.config.ConfigRetriever;
5 | import io.vertx.config.ConfigRetrieverOptions;
6 | import io.vertx.config.ConfigStoreOptions;
7 | import io.vertx.core.*;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.core.logging.Logger;
10 | import logging.ContextLogger;
11 |
12 | public class App extends AbstractVerticle {
13 | private static final String CONFIG_PATH = "app.json";
14 | private static Logger LOGGER = ContextLogger.create();
15 |
16 | @Override
17 | public void start(Promise startPromise) {
18 |
19 | getConfig().setHandler(ar1 -> {
20 | if (ar1.succeeded()) {
21 | LOGGER.info("Config successfully retrived: " + ar1.result().toString());
22 | DeploymentOptions deploymentOptions = new DeploymentOptions().setConfig(ar1.result());
23 | CompositeFuture.all(
24 | deployVerticle(HttpVerticle.class, deploymentOptions),
25 | deployVerticle(UserServiceVerticle.class, deploymentOptions),
26 | deployVerticle(ArticleServiceVerticle.class, deploymentOptions),
27 | deployVerticle(CommentServiceVerticle.class, deploymentOptions),
28 | deployVerticle(MorphiaServiceVerticle.class, deploymentOptions))
29 | .setHandler(ar2 -> {
30 | if (ar2.succeeded()) {
31 | LOGGER.info("Successfully deployed verticals.");
32 | startPromise.complete();
33 | } else {
34 | LOGGER.error("Failed to deploy verticles: " + ar2.cause().getMessage());
35 | startPromise.fail(ar2.cause());
36 | }
37 | });
38 | } else {
39 | startPromise.fail(ar1.cause());
40 | }
41 | });
42 | }
43 |
44 | private Future deployVerticle(Class extends Verticle> clz, DeploymentOptions deploymentOptions){
45 | Promise deploymentFuture = Promise.promise();
46 | vertx.deployVerticle(clz, deploymentOptions, ar ->{
47 | if (ar.succeeded()) {
48 | deploymentFuture.complete();
49 | } else {
50 | deploymentFuture.fail(ar.cause());
51 | }
52 | });
53 | return deploymentFuture.future();
54 | }
55 |
56 | private Future getConfig() {
57 | ConfigStoreOptions co = new ConfigStoreOptions().
58 | setType("file").
59 | setFormat("json").
60 | setConfig(new JsonObject().put("path", CONFIG_PATH));
61 |
62 | ConfigRetrieverOptions cro = new ConfigRetrieverOptions().
63 | addStore(co);
64 |
65 | ConfigRetriever cr = ConfigRetriever.create(vertx, cro);
66 |
67 | return ConfigRetriever.getConfigAsFuture(cr);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/CommentServiceImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.conduit.entities.Comment;
4 | import io.vertx.core.AsyncResult;
5 | import io.vertx.core.Future;
6 | import io.vertx.core.Handler;
7 | import io.vertx.core.Vertx;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.serviceproxy.ServiceProxyBuilder;
10 | import org.bson.types.ObjectId;
11 |
12 | import java.util.List;
13 |
14 | public class CommentServiceImpl implements CommentService {
15 |
16 | private final io.vertx.conduit.services.reactivex.MorphiaService morphiaService;
17 |
18 | public CommentServiceImpl(Vertx vertx){
19 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(MorphiaService.ADDRESS);
20 | MorphiaService delegate = builder.build(MorphiaService.class);
21 | morphiaService = new io.vertx.conduit.services.reactivex.MorphiaService(delegate);
22 | }
23 |
24 | @Override
25 | public void create(JsonObject comment, Handler> resultHandler) {
26 | Comment commentEntity = new Comment(comment);
27 | morphiaService.rxCreateComment(commentEntity)
28 | .subscribe((id, ex) -> {
29 | if (ex == null) {
30 | commentEntity.setId(new ObjectId(id));
31 | resultHandler.handle(Future.succeededFuture(commentEntity));
32 | } else {
33 | resultHandler.handle(Future.failedFuture(ex));
34 | }
35 | });
36 | }
37 |
38 | @Override
39 | public void update(String id, JsonObject article, Handler> resultHandler) {
40 | morphiaService.rxUpdateComment(new JsonObject().put("_id", id), article)
41 | .subscribe((comments, ex) -> handleComment(resultHandler, comments, ex));
42 | }
43 |
44 | @Override
45 | public void get(String id, Handler> resultHandler) {
46 | morphiaService.rxGetComment(new JsonObject().put("_id", id))
47 | .subscribe((comments, ex) -> handleComment(resultHandler, comments, ex));
48 | }
49 |
50 | @Override
51 | public void delete(String id, Handler> resultHandler) {
52 | morphiaService.rxDeleteComment(new JsonObject().put("_id", id))
53 | .subscribe((res, ex) -> {
54 | if (ex == null) {
55 | resultHandler.handle(Future.succeededFuture(res));
56 | } else {
57 | resultHandler.handle(Future.failedFuture(ex));
58 | }
59 | });
60 | }
61 |
62 | private static void handleComment(Handler> resultHandler, List comments, Throwable ex) {
63 | if (comments == null || comments.size() != 1) {
64 | resultHandler.handle(Future.failedFuture(new RuntimeException("Couldn't find unique comment")));
65 | } else {
66 | if (ex == null) {
67 | resultHandler.handle(Future.succeededFuture(comments.get(0)));
68 | } else {
69 | resultHandler.handle(Future.failedFuture(ex));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/ArticleServiceImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.conduit.entities.Article;
4 | import io.vertx.core.AsyncResult;
5 | import io.vertx.core.Future;
6 | import io.vertx.core.Handler;
7 | import io.vertx.core.Vertx;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.ext.mongo.FindOptions;
10 | import io.vertx.ext.mongo.UpdateOptions;
11 | import io.vertx.serviceproxy.ServiceProxyBuilder;
12 | import org.bson.types.ObjectId;
13 |
14 | import java.util.List;
15 |
16 | public class ArticleServiceImpl implements ArticleService {
17 |
18 | private final io.vertx.conduit.services.reactivex.MorphiaService morphiaService;
19 |
20 | public ArticleServiceImpl(Vertx vertx){
21 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(MorphiaService.ADDRESS);
22 | MorphiaService delegate = builder.build(MorphiaService.class);
23 | morphiaService = new io.vertx.conduit.services.reactivex.MorphiaService(delegate);
24 | }
25 |
26 | @Override
27 | public void create(JsonObject article, Handler> resultHandler) {
28 | Article articleEntity = new Article(article);
29 | morphiaService.rxCreateArticle(articleEntity)
30 | .subscribe((id, ex) -> {
31 | if (ex == null) {
32 | articleEntity.setId(new ObjectId(id));
33 | resultHandler.handle(Future.succeededFuture(articleEntity));
34 | } else {
35 | resultHandler.handle(Future.failedFuture(ex));
36 | }
37 | });
38 | }
39 |
40 | @Override
41 | public void update(String slug, JsonObject article, Handler> resultHandler) {
42 | morphiaService.rxUpdateArticle(new JsonObject().put("slug", slug), article)
43 | .subscribe((articles, ex) -> handleArticle(resultHandler, articles, ex));
44 | }
45 |
46 | @Override
47 | public void get(String slug, Handler> resultHandler) {
48 | morphiaService.rxGetArticle(new JsonObject().put("slug", slug))
49 | .subscribe((articles, ex) -> handleArticle(resultHandler, articles, ex));
50 | }
51 |
52 | @Override
53 | public void delete(String slug, Handler> resultHandler) {
54 | morphiaService.rxDeleteArticle(new JsonObject().put("slug", slug))
55 | .subscribe((res, ex) -> {
56 | if (ex == null) {
57 | resultHandler.handle(Future.succeededFuture());
58 | } else {
59 | resultHandler.handle(Future.failedFuture(ex));
60 | }
61 | });
62 | }
63 |
64 | private static void handleArticle(Handler> resultHandler, List articles, Throwable ex) {
65 | if (articles == null || articles.size() != 1) {
66 | resultHandler.handle(Future.failedFuture(new RuntimeException("Couldn't find unique article")));
67 | } else {
68 | if (ex == null) {
69 | resultHandler.handle(Future.succeededFuture(articles.get(0)));
70 | } else {
71 | resultHandler.handle(Future.failedFuture(ex));
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/entities/Base.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.entities;
2 |
3 | import dev.morphia.annotations.Entity;
4 | import dev.morphia.annotations.Id;
5 | import dev.morphia.annotations.Version;
6 | import io.vertx.core.json.JsonObject;
7 | import org.bson.types.ObjectId;
8 |
9 | import java.io.Serializable;
10 | import java.text.DateFormat;
11 | import java.text.ParseException;
12 | import java.text.SimpleDateFormat;
13 | import java.util.Date;
14 | import java.util.Objects;
15 | import java.util.TimeZone;
16 |
17 | @Entity
18 | public abstract class Base implements Serializable {
19 |
20 | public static DateFormat DF;
21 | static {
22 | TimeZone tz = TimeZone.getTimeZone("UTC");
23 | DF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
24 | DF.setTimeZone(tz);
25 | }
26 |
27 | @Id
28 | private ObjectId id;
29 |
30 | @Version
31 | private long version;
32 | private String createUser;
33 | private long createdAt;
34 | private long updatedAt;
35 | private String updateUser;
36 | private boolean isRetired;
37 |
38 | public ObjectId getId() {
39 | return id;
40 | }
41 |
42 | public void setId(ObjectId id) {
43 | this.id = id;
44 | }
45 |
46 | public String getCreateUser() {
47 | return createUser;
48 | }
49 |
50 | public void setCreateUser(String createUser) {
51 | this.createUser = createUser;
52 | }
53 |
54 | public long getCreatedAt() {
55 | return createdAt;
56 | }
57 |
58 | public void setCreatedAt(long createdAt) {
59 | this.createdAt = createdAt;
60 | }
61 |
62 | public long getUpdatedAt() {
63 | return updatedAt;
64 | }
65 |
66 | public void setUpdatedAt(long updatedAt) {
67 | this.updatedAt = updatedAt;
68 | }
69 |
70 | public String getUpdateUser() {
71 | return updateUser;
72 | }
73 |
74 | public void setUpdateUser(String updateUser) {
75 | this.updateUser = updateUser;
76 | }
77 |
78 | public boolean getIsActive() {
79 | return isRetired;
80 | }
81 |
82 | public void setIsActive(boolean isActive) {
83 | this.isRetired = isActive;
84 | }
85 |
86 | protected void toJson(JsonObject json) {
87 | json.put("id", id == null ? null : id.toHexString());
88 | json.put("createdAt", this.createdAt);
89 | json.put("updatedAt", this.updatedAt);
90 | }
91 |
92 | protected void fromJson(JsonObject json) {
93 | String id = json.getString("id");
94 | if (id != null) {
95 | this.id = new ObjectId(id);
96 | }
97 | if (json.getLong("createdAt") != null) {
98 | this.createdAt = json.getLong("createdAt");
99 | }
100 | if (json.getLong("updatedAt") != null) {
101 | this.updatedAt = json.getLong("updatedAt");
102 | }
103 | }
104 |
105 | public long getVersion() {
106 | return version;
107 | }
108 |
109 | public void setVersion(long version) {
110 | this.version = version;
111 | }
112 |
113 | @Override
114 | public boolean equals(Object o) {
115 | if (this == o) return true;
116 | if (!(o instanceof Base)) return false;
117 | Base base = (Base) o;
118 | return Objects.equals(id, base.id);
119 | }
120 |
121 | @Override
122 | public int hashCode() {
123 | return Objects.hash(id);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/ConduitJwtAuthHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import io.vertx.core.AsyncResult;
4 | import io.vertx.core.Future;
5 | import io.vertx.core.Handler;
6 | import io.vertx.core.http.HttpHeaders;
7 | import io.vertx.core.http.HttpServerRequest;
8 | import io.vertx.core.json.JsonArray;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.ext.auth.jwt.JWTAuth;
11 | import io.vertx.ext.web.RoutingContext;
12 | import io.vertx.ext.web.handler.impl.AuthHandlerImpl;
13 | import io.vertx.ext.web.handler.impl.HttpStatusException;
14 |
15 | import java.util.List;
16 | import java.util.Objects;
17 |
18 | public class ConduitJwtAuthHandlerImpl extends AuthHandlerImpl {
19 |
20 | private static final HttpStatusException UNAUTHORIZED = new HttpStatusException(401);
21 | private static final HttpStatusException BAD_REQUEST = new HttpStatusException(400);
22 |
23 | private final JsonObject options = new JsonObject();
24 | private final String type;
25 |
26 | public ConduitJwtAuthHandlerImpl(JWTAuth authProvider, String type) {
27 | super(authProvider, null);
28 | this.type = type;
29 | }
30 |
31 | public ConduitJwtAuthHandlerImpl setAudience(List audience) {
32 | options.put("audience", new JsonArray(audience));
33 | return this;
34 | }
35 |
36 | public ConduitJwtAuthHandlerImpl setIssuer(String issuer) {
37 | options.put("issuer", issuer);
38 | return this;
39 | }
40 |
41 | public ConduitJwtAuthHandlerImpl setIgnoreExpiration(boolean ignoreExpiration) {
42 | options.put("ignoreExpiration", ignoreExpiration);
43 | return this;
44 | }
45 |
46 | @Override
47 | protected String authenticateHeader(RoutingContext context) {
48 | return Constants.AUTH_KEY;
49 | }
50 |
51 | @Override
52 | public void parseCredentials(RoutingContext context, Handler> handler) {
53 |
54 | parseAuthorizationToken(context, false, parseAuthorization -> {
55 | if (parseAuthorization.failed()) {
56 | handler.handle(Future.failedFuture(parseAuthorization.cause()));
57 | return;
58 | }
59 |
60 | handler.handle(Future.succeededFuture(new JsonObject().put("jwt", parseAuthorization.result()).put("options", options)));
61 | });
62 | }
63 |
64 | private void parseAuthorizationToken(RoutingContext ctx, boolean optional, Handler> handler) {
65 |
66 | final HttpServerRequest request = ctx.request();
67 | final String authorization = request.headers().get(HttpHeaders.AUTHORIZATION);
68 |
69 | if (authorization == null) {
70 | if (optional) {
71 | // this is allowed
72 | handler.handle(Future.succeededFuture());
73 | } else {
74 | handler.handle(Future.failedFuture(UNAUTHORIZED));
75 | }
76 | return;
77 | }
78 |
79 | try {
80 | int idx = authorization.indexOf(' ');
81 |
82 | if (idx <= 0) {
83 | handler.handle(Future.failedFuture(BAD_REQUEST));
84 | return;
85 | }
86 |
87 | if (!Objects.equals(type, (authorization.substring(0, idx)))) {
88 | handler.handle(Future.failedFuture(UNAUTHORIZED));
89 | return;
90 | }
91 |
92 | handler.handle(Future.succeededFuture(authorization.substring(idx + 1)));
93 | } catch (RuntimeException e) {
94 | handler.handle(Future.failedFuture(e));
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/entities/Article.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.entities;
2 |
3 | import dev.morphia.annotations.*;
4 | import io.vertx.codegen.annotations.DataObject;
5 | import io.vertx.core.json.JsonArray;
6 | import io.vertx.core.json.JsonObject;
7 |
8 | import javax.validation.Valid;
9 | import javax.validation.constraints.NotEmpty;
10 | import java.util.Date;
11 | import java.util.List;
12 |
13 | @Entity("articles")
14 | @Indexes(@Index(fields = { @Field("slug") }, options = @IndexOptions(unique = true)))
15 | @DataObject(generateConverter = true)
16 | public class Article extends Base {
17 |
18 | public Article(){}
19 |
20 | public Article(JsonObject jsonObject) {
21 | fromJson(jsonObject);
22 | }
23 |
24 | @NotEmpty
25 | private String slug;
26 |
27 | @NotEmpty
28 | private String title;
29 |
30 | private String description;
31 |
32 | private String body;
33 |
34 | private List tagList;
35 |
36 | private boolean favorited;
37 |
38 | private int favoritesCount;
39 |
40 | @Reference (idOnly = true, lazy = true)
41 | @Valid
42 | private User author;
43 |
44 | public String getSlug() {
45 | return slug;
46 | }
47 |
48 | @Reference (idOnly = true, lazy = true)
49 | @Valid
50 | private List comments;
51 |
52 | public void setSlug(String slug) {
53 | this.slug = slug;
54 | }
55 |
56 | public String getTitle() {
57 | return title;
58 | }
59 |
60 | public void setTitle(String title) {
61 | this.title = title;
62 | }
63 |
64 | public String getDescription() {
65 | return description;
66 | }
67 |
68 | public void setDescription(String description) {
69 | this.description = description;
70 | }
71 |
72 | public String getBody() {
73 | return body;
74 | }
75 |
76 | public void setBody(String body) {
77 | this.body = body;
78 | }
79 |
80 | public List getTagList() {
81 | return tagList;
82 | }
83 |
84 | public void setTagList(List tagList) {
85 | this.tagList = tagList;
86 | }
87 |
88 | public boolean isFavorited() {
89 | return favorited;
90 | }
91 |
92 | public void setFavorited(boolean favorited) {
93 | this.favorited = favorited;
94 | }
95 |
96 | public int getFavoritesCount() {
97 | return favoritesCount;
98 | }
99 |
100 | public void setFavoritesCount(int favoritesCount) {
101 | this.favoritesCount = favoritesCount;
102 | }
103 |
104 | public User getAuthor() {
105 | return author;
106 | }
107 |
108 | public void setAuthor(User author) {
109 | this.author = author;
110 | }
111 |
112 | public JsonObject toJson() {
113 | JsonObject json = new JsonObject();
114 | ArticleConverter.toJson(this, json);
115 | super.toJson(json);
116 | // To make postman test script happy
117 | if (json.getJsonArray("tagList") == null) {
118 | json.put("tagList", new JsonArray());
119 | }
120 | return json;
121 | }
122 |
123 | protected void fromJson(JsonObject jsonObject) {
124 | super.fromJson(jsonObject);
125 | ArticleConverter.fromJson(jsonObject, this);
126 | }
127 |
128 | public JsonObject toJsonFor(User user) {
129 | JsonObject json = toJson();
130 | json.put("createdAt", DF.format(new Date(getCreatedAt())));
131 | json.put("updatedAt", DF.format(new Date(getCreatedAt())));
132 | json.put("author", author.toProfileJsonFor(user));
133 | json.put("favorited", user != null && user.isFavorite(getSlug()));
134 | return json;
135 | }
136 |
137 | public List getComments() {
138 | return comments;
139 | }
140 |
141 | public void setComments(List comments) {
142 | this.comments = comments;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/entities/User.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.entities;
2 |
3 | import dev.morphia.annotations.*;
4 | import io.vertx.codegen.annotations.DataObject;
5 | import io.vertx.core.json.JsonObject;
6 |
7 | import javax.validation.constraints.Email;
8 | import javax.validation.constraints.NotEmpty;
9 | import javax.validation.constraints.Pattern;
10 | import java.util.ArrayList;
11 | import java.util.Collections;
12 | import java.util.List;
13 | import java.util.stream.Collectors;
14 |
15 | import static io.vertx.conduit.handlers.Constants.USER;
16 |
17 |
18 | @Entity("users")
19 | @Indexes({@Index(fields = { @Field("username") }, options = @IndexOptions(unique = true)),
20 | @Index(fields = { @Field("email") }, options = @IndexOptions(unique = true))})
21 | @DataObject(generateConverter = true)
22 | public class User extends Base {
23 |
24 | public User() {}
25 |
26 | public User(JsonObject json) {
27 | fromJson(json);
28 | }
29 |
30 | @NotEmpty
31 | @Pattern(regexp="[a-zA-Z0-9_]+")
32 | private String username;
33 |
34 | @NotEmpty
35 | @Email
36 | private String email;
37 |
38 | @NotEmpty
39 | private String password;
40 |
41 | private String bio;
42 |
43 | private String image;
44 |
45 | private final List favorites = new ArrayList<>();
46 |
47 | @Reference (idOnly = true, lazy = true)
48 | private final List following = new ArrayList<>();
49 |
50 | public void setUsername(String username) {
51 | this.username = username;
52 | }
53 |
54 | public String getEmail() {
55 | return email;
56 | }
57 |
58 | public void setEmail(String email) {
59 | this.email = email;
60 | }
61 |
62 | public String getPassword() {
63 | return password;
64 | }
65 |
66 | public void setPassword(String password) {
67 | this.password = password;
68 | }
69 |
70 | public String getBio() {
71 | return bio;
72 | }
73 |
74 | public void setBio(String bio) {
75 | this.bio = bio;
76 | }
77 |
78 | public String getImage() {
79 | return image;
80 | }
81 |
82 | public void setImage(String image) {
83 | this.image = image;
84 | }
85 |
86 | public JsonObject toJson() {
87 | JsonObject json = new JsonObject();
88 | UserConverter.toJson(this, json);
89 | super.toJson(json);
90 | return json;
91 | }
92 |
93 | protected void fromJson(JsonObject jsonObject) {
94 | super.fromJson(jsonObject);
95 | UserConverter.fromJson(jsonObject, this);
96 | }
97 |
98 | public String getUsername() {
99 | return username;
100 | }
101 |
102 | public JsonObject toAuthJson() {
103 | JsonObject retJson = new JsonObject();
104 | retJson.put("bio", bio);
105 | retJson.put("email", email);
106 | retJson.put("image", image);
107 | retJson.put("username", username);
108 |
109 | return retJson;
110 | }
111 |
112 | public JsonObject toProfileJsonFor(User user) {
113 | JsonObject retJson = new JsonObject();
114 | retJson.put("bio", bio);
115 | retJson.put("image", image);
116 | retJson.put("username", username);
117 | retJson.put("following", user != null && user.isFollowing(this));
118 |
119 | return retJson;
120 | }
121 |
122 | public void addFavorite(String slug) {
123 | favorites.add(slug);
124 | }
125 |
126 | public void removeFavorite(String slug) {
127 | favorites.remove(slug);
128 | }
129 |
130 | public boolean isFavorite(String id) {
131 | return this.favorites.contains(id);
132 | }
133 |
134 | public void follow(User user) {
135 | if (!this.following.contains(user)) {
136 | this.following.add(user);
137 | }
138 | }
139 |
140 | public void unfollow(User user) {
141 | this.following.remove(user);
142 | }
143 |
144 | public boolean isFollowing(User user) {
145 | return this.following.contains(user);
146 | }
147 |
148 | public List getFollowingUsers() {
149 | return Collections.unmodifiableList(this.following);
150 | }
151 |
152 | public List getFavorites() {
153 | return Collections.unmodifiableList(this.favorites);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | - Clone the repo
4 | - Make sure you have Java1.8+ JDK installed
5 | - Open the project in your IDE and allow gradle to finish downloading all dependencies
6 | - A trick for Intellij, If you don't see your gradle menu refresh with new tasks and dependencies after it's done processing, try restarting your IDE
7 | - Install Mongodb community version and launch it in the background
8 | - Install Robo 3T if you want to manually examine the values in the database
9 | - Compile and try running all the unit tests under `/vertx-examples/conduit/src/test/java/`
10 | - Run mongodb in terminal `mongod --config /usr/local/etc/mongod.conf --fork`
11 | - `Gradle tasks -> application -> vertxRun` to run the application
12 | - Run the test script from here `https://github.com/gothinkster/realworld/tree/master/api`
13 |
14 | # Application structure
15 |
16 | - Entity class: defines the data object and the translation from and to json
17 | - Handler class: defines routes and invoke the appropriate service(s) for processing
18 | - Service class: performs entity specific CRUD operations by invoking the database service
19 | - Database Service class: performs generic database operations using Json as the media
20 | - Service Verticles: instantiates a service
21 | - Http Verticle: sets up all the routes
22 | - App Verticle: main verticle that creates all the other verticles and entry point of the application
23 |
24 | # Gradle tasks
25 | - `application >- vertxRun` runs the application. It supports a feature similar to nodmon in Node.js, meaning you can make code changes and it'll recompile and relaunch the app on the fly
26 | - `application -> vertxDebug` launches the application in remote debuging mode. You can then start up a remote debugging session on port 5005 to tap into the application
27 | - `build -> generate` creates all the auto-generated classes. This is run as part of the build step and you shoulnd't have to run it manually
28 | - `shadow -> shadowJar` creates a fat jar file
29 | - more info can be found here https://github.com/jponge/vertx-gradle-plugin
30 |
31 | # Development steps
32 |
33 | - Setup workspace using vertx gradle plugin
34 | - Setup first http verticle with hello world
35 | - Setup config file in resources
36 | - Setup first route with a simple handler for post request - register new user
37 | - Setup second route with a simple handler for get request - get user
38 | - Setup jwt auth handler for protected routes
39 | - Setup a user service to process the requests
40 | - Use bcrypt for password hash
41 | - Connect the handler and the service using event bus
42 | - Use service proxy instead of working with event bus directly
43 | - Create a UserServiceVerticle to publish the service
44 | - Move all user handlers into its own class
45 | - Create annotation for configuring routes
46 | - CRUD for user with authentication
47 | - Rxify the MongoDb service and User service.
48 | - Add unit test for mongodb service
49 | - Create Morphia service to replace MorgonDb service
50 | - Make sure Morphia annotation works regarding index and validation
51 | - Use Morphia service in handlers
52 | - CRUD articles
53 | - add favorite/unfavorite
54 | - CRUD comments
55 | - add follow/unfollow
56 | - add tags
57 | - add query routes
58 | - add annotation for middleware methods for the sake of clarity
59 | - add user unit tests
60 | - add article unit tests
61 | - add comment unit tests
62 | - add query unit tests
63 | - Put all middleware methods in a shared base class
64 | - populate base fields
65 | - cleanup string literals
66 | - add custom jwt handler to expect "Token" instead of "Bearer"
67 | - Plow through the Postman test script from the realworld project page and make everything pass.
68 |
69 | # After thoughts
70 |
71 | - Wasn't a fan of how ObjectId works and the fact that it can be null
72 | - Certain join-like queries using morphia and mongodb is quite painful
73 | - Still can't avoid relational data model
74 | - rxJava2 is a little rough to debug, and is probably an overkill.
75 | - CompletableFuture should suffice since we are only dealing with rx Singles, not streams. And we don't need to deal with threading because of Vert.x, throwing away a major benefit of rxJava2
76 | - Limitations of Vert.x
77 | - lack of flexibility regarding message codec for event bus messages (and hence service proxy)
78 | - lack of ability to customize transport layer (multicast, retry, back pressure and etc)
79 | - lack of support of CompletableFuture within the service proxy framework (Can't auto generate service proxy that uses CompletableFuture)
80 | p.s. Any insight/comment/correction is welcome
81 |
82 | # Credits (Inspired by)
83 | - greyseal's work at https://github.com/greyseal/vertx-realworld-example-app
84 | - skanjo's work at https://github.com/skanjo/realworld-vertx
85 |
--------------------------------------------------------------------------------
/conduit/src/test/java/services/MongoServiceTest.java:
--------------------------------------------------------------------------------
1 | package services;
2 |
3 | import io.vertx.conduit.entities.User;
4 | import io.vertx.conduit.services.MongoDbServiceImpl;
5 | import io.vertx.conduit.services.reactivex.MongoDbService;
6 | import io.vertx.core.json.JsonObject;
7 | import io.vertx.core.logging.Logger;
8 | import io.vertx.ext.mongo.FindOptions;
9 | import io.vertx.ext.mongo.UpdateOptions;
10 | import io.vertx.ext.unit.Async;
11 | import io.vertx.ext.unit.TestContext;
12 | import io.vertx.ext.unit.junit.VertxUnitRunner;
13 | import io.vertx.reactivex.core.Vertx;
14 | import io.vertx.reactivex.ext.mongo.MongoClient;
15 | import logging.ContextLogger;
16 | import org.junit.After;
17 | import org.junit.Before;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 |
21 | import static org.junit.Assert.assertTrue;
22 |
23 | @RunWith(VertxUnitRunner.class)
24 | public class MongoServiceTest {
25 |
26 | private static final String USER_COLLETION = "users";
27 | private static Logger LOGGER = ContextLogger.create();
28 |
29 | private Vertx vertx;
30 | private MongoDbService mongoDbService;
31 |
32 | @Before
33 | public void setup(TestContext tc) {
34 | this.vertx = Vertx.vertx();
35 | JsonObject config = new JsonObject().put("db_name", "conduit_test")
36 | .put("connection_string", "mongodb://localhost:27017").put("useObjectId", true);
37 | MongoClient mongoClient = MongoClient.createShared(vertx, config);
38 | MongoDbServiceImpl delegate = new MongoDbServiceImpl(mongoClient, tc.asyncAssertSuccess());
39 | this.mongoDbService = new io.vertx.conduit.services.reactivex.MongoDbService(delegate);
40 | }
41 |
42 | @Test
43 | public void TestWriteUser(TestContext tc) {
44 | Async async = tc.async();
45 |
46 | System.out.println("Test writing user");
47 |
48 | JsonObject user = new JsonObject();
49 | user.put("password", "123");
50 | user.put("bio", "abc");
51 | user.put("email", "1@2.com");
52 | user.put("username", "xyz");
53 |
54 | this.mongoDbService.rxInsertOne(USER_COLLETION, user)
55 | .map(id -> {
56 | LOGGER.info("Creating document");
57 | user.put("id", user.getString("_id"));
58 | user.remove("_id");
59 | User userEntity = new User(user);
60 | return userEntity;
61 | }).flatMap(userEntity -> {
62 | LOGGER.info("Reading document");
63 | return this.mongoDbService.rxFindById(USER_COLLETION, userEntity.getId().toHexString(), null);
64 | }).flatMap(json -> {
65 | LOGGER.info("Updating document");
66 | json.put("id", json.getString("_id"));
67 | json.remove("_id");
68 | assertTrue(user.equals(json));
69 | JsonObject updateJson = new JsonObject();
70 | updateJson.put("email", "3@4.com");
71 | user.put("email", "3@4.com");
72 | return this.mongoDbService.rxFindOneAndUpdate(USER_COLLETION, new JsonObject().put("_id", user.getString("id")), updateJson, new FindOptions(), new UpdateOptions());
73 | }).flatMap(json -> {
74 | LOGGER.info("Reading updated document");
75 | // FIXME why does this return a nested object id?
76 | return this.mongoDbService.rxFindById(USER_COLLETION, json.getJsonObject("_id").getString("$oid"), null);
77 | }).flatMap(json -> {
78 | json.put("id", json.getString("_id"));
79 | json.remove("_id");
80 | assertTrue(user.equals(json));
81 | LOGGER.info("Deleting document: " + json);
82 | return this.mongoDbService.rxDelete(USER_COLLETION, new JsonObject().put("_id", user.getString("id")));
83 | }).subscribe((res, e) -> {
84 | if (e == null) {
85 | async.complete();
86 | } else {
87 | tc.fail(e);
88 | }
89 | });
90 | }
91 |
92 | @After
93 | public void tearDown(TestContext tc) {
94 | vertx.setTimer(1000, t -> { System.out.println("timer complete"); });
95 | vertx.close(tc.asyncAssertSuccess());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import io.vertx.conduit.entities.User;
4 | import io.vertx.core.AsyncResult;
5 | import io.vertx.core.Future;
6 | import io.vertx.core.Handler;
7 | import io.vertx.core.Vertx;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.core.logging.Logger;
10 | import io.vertx.serviceproxy.ServiceProxyBuilder;
11 | import logging.ContextLogger;
12 | import org.bson.types.ObjectId;
13 |
14 | import java.util.List;
15 |
16 |
17 | public class UserServiceImpl implements UserService {
18 |
19 | private static final Logger LOGGER = ContextLogger.create();
20 |
21 | // active user being processed
22 | private final io.vertx.conduit.services.reactivex.MorphiaService morphiaService;
23 |
24 | public UserServiceImpl(Vertx vertx) {
25 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(MorphiaService.ADDRESS);
26 | MorphiaService delegate = builder.build(MorphiaService.class);
27 | morphiaService = new io.vertx.conduit.services.reactivex.MorphiaService(delegate);
28 | }
29 |
30 | @Override
31 | public void create(JsonObject user, Handler> resultHandler) {
32 | User userEntity = new User(user);
33 | morphiaService.rxCreateUser(userEntity)
34 | .subscribe((id, ex) -> {
35 | if (ex == null) {
36 | userEntity.setId(new ObjectId(id));
37 | resultHandler.handle(Future.succeededFuture(userEntity));
38 | } else {
39 | resultHandler.handle(Future.failedFuture(ex));
40 | }
41 | });
42 | }
43 |
44 | @Override
45 | public void getByEmail(String email, Handler> resultHandler) {
46 | morphiaService.rxGetUser(new JsonObject().put("email", email))
47 | .subscribe((users, ex) -> handleUser(resultHandler, users, ex));
48 |
49 | }
50 |
51 | @Override
52 | public void get(JsonObject query, Handler> resultHandler) {
53 |
54 | morphiaService.rxGetUser(query)
55 | .subscribe((users, ex) -> handleUser(resultHandler, users, ex));
56 | }
57 |
58 | @Override
59 | public void getById(String id, Handler> resultHandler) {
60 |
61 | morphiaService.rxGetUser(new JsonObject().put("_id", id))
62 | .subscribe((users, ex) -> handleUser(resultHandler, users, ex));
63 | }
64 |
65 | @Override
66 | public void update(String id, JsonObject update, Handler> resultHandler) {
67 | // Have to filter on all unique fields for mongodb to replace the document instead of inserting a new one
68 | morphiaService.rxUpdateUser(new JsonObject().put("_id", id), update)
69 | .subscribe((users, ex) -> handleUser(resultHandler, users, ex));
70 | }
71 |
72 | private static void handleUser(Handler> resultHandler, List users, Throwable ex) {
73 |
74 | if (users == null || users.size() != 1) {
75 | resultHandler.handle(Future.failedFuture(new RuntimeException("Couldn't find unique user")));
76 | }
77 | else {
78 | if (ex == null) {
79 | resultHandler.handle(Future.succeededFuture(users.get(0)));
80 | } else {
81 | resultHandler.handle(Future.failedFuture(ex));
82 | }
83 | }
84 | }
85 |
86 | public void getFavoriteCount(String slug, Handler> resultHandler){
87 | morphiaService.rxGetUser(new JsonObject().put("favorites", slug))
88 | .subscribe((users, ex) -> {
89 | if (ex == null) {
90 | resultHandler.handle(Future.succeededFuture(users.size()));
91 | } else {
92 | resultHandler.handle(Future.failedFuture(ex));
93 | }
94 | });
95 | }
96 |
97 | @Override
98 | public void deleteByUsername(String username, Handler> resultHandler) {
99 | morphiaService.rxDeleteUser(new JsonObject().put("username", username))
100 | .subscribe((deleteCount, ex) -> {
101 | if (ex == null) {
102 | resultHandler.handle(Future.succeededFuture(deleteCount));
103 | } else {
104 | resultHandler.handle(Future.failedFuture(ex));
105 | }
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/ConduitHandler.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import io.vertx.conduit.entities.User;
4 | import io.vertx.conduit.services.ArticleService;
5 | import io.vertx.conduit.services.CommentService;
6 | import io.vertx.conduit.services.MorphiaService;
7 | import io.vertx.conduit.services.UserService;
8 | import io.vertx.core.Vertx;
9 | import io.vertx.core.json.JsonObject;
10 | import io.vertx.ext.web.RoutingContext;
11 | import io.vertx.serviceproxy.ServiceProxyBuilder;
12 | import routerutils.BaseHandler;
13 | import routerutils.Middleware;
14 |
15 | public class ConduitHandler extends BaseHandler {
16 |
17 | protected final io.vertx.conduit.services.reactivex.ArticleService articleService;
18 | protected final io.vertx.conduit.services.reactivex.UserService userService;
19 | protected final io.vertx.conduit.services.reactivex.CommentService commentService;
20 | protected final io.vertx.conduit.services.reactivex.MorphiaService morphiaService;
21 |
22 | protected ConduitHandler(Vertx vertx) {
23 | super(vertx);
24 |
25 | {
26 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(ArticleService.ADDRESS);
27 | ArticleService delegate = builder.build(ArticleService.class);
28 | this.articleService = new io.vertx.conduit.services.reactivex.ArticleService(delegate);
29 | }
30 |
31 | {
32 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(UserService.ADDRESS);
33 | UserService delegate = builder.build(UserService.class);
34 | this.userService = new io.vertx.conduit.services.reactivex.UserService(delegate);
35 | }
36 |
37 | {
38 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(CommentService.ADDRESS);
39 | CommentService delegate = builder.build(CommentService.class);
40 | this.commentService = new io.vertx.conduit.services.reactivex.CommentService(delegate);
41 | }
42 |
43 | {
44 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(MorphiaService.ADDRESS);
45 | MorphiaService delegate = builder.build(MorphiaService.class);
46 | this.morphiaService = new io.vertx.conduit.services.reactivex.MorphiaService(delegate);
47 | }
48 | }
49 |
50 |
51 | @Middleware
52 | public void extractUser(RoutingContext event) {
53 | if (event.get(Constants.USER_ID) == null) {
54 | event.next();
55 | return;
56 | }
57 | userService.rxGetById(event.get(Constants.USER_ID))
58 | .subscribe((user, ex) -> {
59 | if (ex == null) {
60 | event.put(Constants.USER, user);
61 | event.next();
62 | } else {
63 | event.fail(ex);
64 | }
65 | });
66 | }
67 |
68 | @Middleware
69 | public void extractProfile(RoutingContext event) {
70 | String username = event.request().getParam("username");
71 | userService.rxGet(new JsonObject().put("username", username))
72 | .subscribe((user, ex) -> {
73 | if (ex == null) {
74 | event.put("profile", user);
75 | event.next();
76 | } else {
77 | event.fail(ex);
78 | }
79 | });
80 | }
81 |
82 | @Middleware
83 | public void extractArticle(RoutingContext event) {
84 | String slug = event.request().getParam(Constants.ARTICLE);
85 | if (slug != null) {
86 | articleService.rxGet(slug)
87 | .subscribe((article, ex) -> {
88 | if (ex == null) {
89 | event.put(Constants.ARTICLE, article);
90 | event.next();
91 | } else {
92 | event.fail(ex);
93 | }
94 | });
95 | } else {
96 | event.next();
97 | }
98 | }
99 |
100 | @Middleware
101 | public void extractComment(RoutingContext event) {
102 | String commentId = event.request().getParam(Constants.COMMENT);
103 | commentService.rxGet(commentId)
104 | .subscribe((comment, ex) -> {
105 | if (ex == null) {
106 | event.put(Constants.COMMENT, comment);
107 | event.next();
108 | } else {
109 | event.fail(ex);
110 | }
111 | });
112 | }
113 |
114 | protected void setCreateFields(RoutingContext event, JsonObject jsonObject) {
115 | jsonObject.put("createdAt", System.currentTimeMillis());
116 | User user = event.get(Constants.USER);
117 | if (user != null) {
118 | jsonObject.put("createUser", user.getUsername());
119 | }
120 | }
121 |
122 | protected void setUpdateFields(RoutingContext event, JsonObject jsonObject) {
123 | jsonObject.put("updatedAt", System.currentTimeMillis());
124 | User user = event.get(Constants.USER);
125 | if (user != null) {
126 | jsonObject.put("updateUser", user.getUsername());
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS='"-Xmx64m"'
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/conduit/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS='"-Xmx64m"'
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/common/src/main/java/routerutils/HandlerProcessor.java:
--------------------------------------------------------------------------------
1 | package routerutils;
2 |
3 | import logging.ContextLogger;
4 | import io.vertx.core.Handler;
5 | import io.vertx.core.http.HttpMethod;
6 | import io.vertx.core.logging.Logger;
7 | import io.vertx.ext.web.Route;
8 | import io.vertx.ext.web.Router;
9 | import io.vertx.ext.web.RoutingContext;
10 | import io.vertx.ext.web.handler.AuthHandler;
11 |
12 | import java.lang.annotation.Annotation;
13 | import java.lang.reflect.Method;
14 | import java.lang.reflect.Modifier;
15 | import java.util.*;
16 | import java.util.stream.Collectors;
17 |
18 | /*
19 | Inspired by greyseal's work
20 | https://github.com/greyseal/vertx-realworld-example-app/blob/master/vertx-boot/src/main/java/com/greyseal/vertx/boot/annotation/AnnotationProcessor.java
21 | */
22 |
23 | public final class HandlerProcessor {
24 |
25 | private static Logger LOGGER = ContextLogger.create();
26 |
27 | public static void buildHandler(final Router router, final List extends Handler> preHandlers, final H handler, final AuthHandler authHandler) {
28 | Class> clazz = handler.getClass();
29 | RouteConfig baseRouteConfig = clazz.getAnnotation(RouteConfig.class);
30 | if (baseRouteConfig != null) {
31 | String[] baseConsumes = baseRouteConfig.consumes();
32 | String[] baseProduces = baseRouteConfig.produces();
33 |
34 | String basePath = baseRouteConfig.path().length() != 0 ? baseRouteConfig.path() : "";
35 | Set methods = getMethodsAnnotatedWith(clazz, RouteConfig.class);
36 | String[] baseMiddlewares = baseRouteConfig.middlewares();
37 | List baseMiddlewareMethods = getMiddlewareMethods(clazz, baseMiddlewares);
38 |
39 | methods.forEach(method -> {
40 | RouteConfig annotation = method.getAnnotation(RouteConfig.class);
41 | if (!Modifier.isPublic(method.getModifiers())) {
42 | throw new RuntimeException("Method must be public to work with RouteConfig " + clazz.getName() + "#" + method.getName());
43 | }
44 | String[] methodConsumes = annotation.consumes();
45 | String[] methodProduces = annotation.produces();
46 | String methodPath = annotation.path();
47 | String[] middlewares = annotation.middlewares();
48 | HttpMethod httpMethod = annotation.method();
49 | boolean authRequired = annotation.authRequired();
50 | List middlewareMethods = getMiddlewareMethods(clazz, middlewares);
51 |
52 | String path = basePath + methodPath;
53 | Route route = router.route(httpMethod, path);
54 | setMediaType(route, methodConsumes.length != 0 ? methodConsumes : baseConsumes , false);
55 | setMediaType(route, methodProduces.length != 0 ? methodProduces : baseProduces , true);
56 |
57 | if (authRequired) {
58 | if (authHandler == null) {
59 | throw new IllegalArgumentException("authHandler must be specified when authRequired = true");
60 | }
61 | route.handler(authHandler);
62 | }
63 |
64 | if (preHandlers != null) {
65 | preHandlers.forEach(h -> route.handler(h));
66 | }
67 |
68 | baseMiddlewareMethods.forEach(m -> createHandler(handler, m, route));
69 | middlewareMethods.forEach(m -> createHandler(handler, m, route));
70 |
71 | createHandler(handler, method, route);
72 | });
73 | }
74 | }
75 |
76 | private static List getMiddlewareMethods(Class> clazz, String[] baseMiddlewares) {
77 | return Arrays.asList(baseMiddlewares)
78 | .stream()
79 | .map(s -> {
80 | try {
81 | return clazz.getMethod(s, RoutingContext.class);
82 | } catch (NoSuchMethodException e) {
83 | throw new IllegalArgumentException("Middleware method not properly defined. Must take RoutingContext as argument " + clazz.getName() + "#" + s);
84 | }
85 | })
86 | .collect(Collectors.toList());
87 | }
88 |
89 | private static void createHandler(final H handler, final Method method, final Route route) {
90 | route.handler(event -> {
91 | try {
92 | method.invoke(handler, event);
93 | } catch (Exception e) {
94 | LOGGER.error("Error calling handler {}#{} {}", handler.getClass(), method.getName(), e);
95 | }
96 | });
97 | }
98 |
99 | private static void setMediaType(final Route route, final String[] mediaTypes, final boolean isProduced) {
100 | if (null != mediaTypes && mediaTypes.length > 0) {
101 | Arrays.asList(mediaTypes).forEach(contentType -> {
102 | if (!isProduced) {
103 | route.consumes(contentType);
104 | } else {
105 | route.produces(contentType);
106 | }
107 | });
108 | }
109 | }
110 |
111 | private static Set getMethodsAnnotatedWith(final Class> type,
112 | final Class extends Annotation> annotation) {
113 | final List methodNames = new ArrayList<>();
114 | final Set methods = new HashSet<>();
115 | Class> clazz = type;
116 | while (clazz != Object.class) {
117 | final List allMethods = new ArrayList(Arrays.asList(clazz.getDeclaredMethods()));
118 | for (final Method method : allMethods) {
119 | if (method.isAnnotationPresent(annotation)) {
120 | if (!methodNames.contains(method.getName())) {
121 | methods.add(method);
122 | methodNames.add(method.getName());
123 | }
124 | }
125 | }
126 | clazz = clazz.getSuperclass();
127 | }
128 | return methods;
129 | }
130 | }
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/MongoDbServiceImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import com.mongodb.MongoWriteException;
4 | import io.vertx.core.AsyncResult;
5 | import io.vertx.core.Future;
6 | import io.vertx.core.Handler;
7 | import io.vertx.core.json.JsonObject;
8 | import io.vertx.ext.mongo.FindOptions;
9 | import io.vertx.ext.mongo.MongoClientDeleteResult;
10 | import io.vertx.ext.mongo.MongoClientUpdateResult;
11 | import io.vertx.ext.mongo.UpdateOptions;
12 | import io.vertx.reactivex.ext.mongo.MongoClient;
13 |
14 | import java.util.List;
15 |
16 | public class MongoDbServiceImpl implements MongoDbService {
17 |
18 | private final MongoClient client;
19 |
20 | public MongoDbServiceImpl(final MongoClient mongoClient, final Handler> readyHandler) {
21 | this.client = mongoClient;
22 | this.client.rxGetCollections().subscribe(resp -> {
23 | readyHandler.handle(Future.succeededFuture(this));
24 | }, cause -> {
25 | readyHandler.handle(Future.failedFuture(cause));
26 | });
27 | }
28 |
29 | @Override
30 | public void findOne(final String collection, final JsonObject query, final JsonObject fields, final Handler> resultHandler) {
31 | try {
32 | client.rxFindOne(collection, query, fields).subscribe(resp -> {
33 | resultHandler.handle(Future.succeededFuture(resp));
34 | }, cause -> {
35 | resultHandler.handle(Future.failedFuture(cause));
36 | });
37 | } catch (Exception ex) {
38 | resultHandler.handle(Future.failedFuture(ex));
39 | }
40 | }
41 |
42 | @Override
43 | public void findById(final String collection, final String id, final JsonObject fields, final Handler> resultHandler) {
44 | findOne(collection, new JsonObject().put("_id", id), fields, resultHandler);
45 | }
46 |
47 | @Override
48 | public void find(final String collection, final JsonObject query, final FindOptions options, Handler>> resultHandler) {
49 | try {
50 | client.rxFindWithOptions(collection, query, options).subscribe(resp -> {
51 | resultHandler.handle(Future.succeededFuture(resp));
52 | }, cause -> {
53 | resultHandler.handle(Future.failedFuture(cause));
54 | });
55 | } catch (Exception ex) {
56 | resultHandler.handle(Future.failedFuture(ex));
57 | }
58 | }
59 |
60 | @Override
61 | public void insertOne(final String collection, final JsonObject document, final Handler> resultHandler) {
62 | try {
63 | // make sure _id field doesn't exist to force database create one
64 | document.remove("id");
65 | client.rxInsert(collection, document).subscribe(resp -> {
66 | resultHandler.handle(Future.succeededFuture(resp));
67 | }, cause -> {
68 | final MongoWriteException mwx = (MongoWriteException) cause;
69 | if (mwx.getCode() == 11000) {
70 | resultHandler.handle(Future.failedFuture(new RuntimeException("DuplicateEntity")));
71 | } else {
72 | resultHandler.handle(Future.failedFuture(cause));
73 | }
74 | });
75 | } catch (Exception ex) {
76 | resultHandler.handle(Future.failedFuture(ex));
77 | }
78 | }
79 |
80 | @Override
81 |
82 | public void upsert(final String collection, final JsonObject query, final JsonObject toUpdate, final UpdateOptions options, Handler> resultHandler) {
83 | try {
84 | client.rxUpdateCollectionWithOptions(collection, query, toUpdate, options).subscribe(resp -> {
85 | resultHandler.handle(Future.succeededFuture(resp));
86 | }, cause -> {
87 | resultHandler.handle(Future.failedFuture(cause));
88 | });
89 | } catch (Exception ex) {
90 | resultHandler.handle(Future.failedFuture(ex));
91 | }
92 | }
93 |
94 | @Override
95 | public void findOneAndUpdate(final String collection, final JsonObject query, final JsonObject toUpdate, final FindOptions findOptions, final UpdateOptions updateOptions, final Handler> resultHandler) {
96 | try {
97 | client.rxFindOneAndUpdateWithOptions(collection, query, new JsonObject().put("$set", toUpdate), findOptions, updateOptions).subscribe(resp -> {
98 | resultHandler.handle(Future.succeededFuture(resp));
99 | }, cause -> {
100 | resultHandler.handle(Future.failedFuture(cause));
101 | });
102 | } catch (Exception ex) {
103 | resultHandler.handle(Future.failedFuture(ex));
104 | }
105 | }
106 |
107 | @Override
108 | public void findOneAndReplace(final String collection, final JsonObject query, final JsonObject replacement, final FindOptions findOptions, final UpdateOptions updateOptions, final Handler> resultHandler) {
109 | try {
110 | client.rxFindOneAndReplaceWithOptions(collection, query, replacement, findOptions, updateOptions).subscribe(resp -> {
111 | resultHandler.handle(Future.succeededFuture(resp));
112 | }, cause -> {
113 | resultHandler.handle(Future.failedFuture(cause));
114 | });
115 | } catch (Exception ex) {
116 | resultHandler.handle(Future.failedFuture(ex));
117 | }
118 | }
119 |
120 | @Override
121 | public void delete(final String collection, final JsonObject query, Handler> resultHandler){
122 | try {
123 | client.rxRemoveDocuments(collection, query).subscribe(resp -> {
124 | resultHandler.handle(Future.succeededFuture(resp));
125 | }, cause -> {
126 | resultHandler.handle(Future.failedFuture(cause));
127 | });
128 | } catch (Exception ex) {
129 | resultHandler.handle(Future.failedFuture(ex));
130 | }
131 | }
132 |
133 | @Override
134 | public void close() {
135 | client.close();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/UserHandler.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.conduit.entities.User;
5 | import io.vertx.conduit.services.MorphiaServiceOperator;
6 | import io.vertx.conduit.services.UserService;
7 | import io.vertx.core.Vertx;
8 | import io.vertx.core.http.HttpMethod;
9 | import io.vertx.core.json.Json;
10 | import io.vertx.core.json.JsonObject;
11 | import io.vertx.ext.auth.jwt.JWTAuth;
12 | import io.vertx.ext.auth.jwt.JWTOptions;
13 | import io.vertx.ext.web.RoutingContext;
14 | import io.vertx.serviceproxy.ServiceProxyBuilder;
15 | import org.springframework.security.crypto.bcrypt.BCrypt;
16 | import routerutils.RouteConfig;
17 |
18 | @RouteConfig(path="/api", produces = "application/json")
19 | public class UserHandler extends ConduitHandler {
20 |
21 | private final JWTAuth jwtAuth;
22 |
23 | public UserHandler(Vertx vertx, JWTAuth jwtAuth) {
24 | super(vertx);
25 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(UserService.ADDRESS);
26 | this.jwtAuth = jwtAuth;
27 | }
28 |
29 | private void appendJwt(JsonObject user, String id) {
30 | JsonObject principal = new JsonObject();
31 | principal.put("id", id);
32 | principal.put("username", user.getString("username"));
33 | user.put(Constants.AUTH_KEY, jwtAuth.generateToken(principal, new JWTOptions().setExpiresInMinutes(120)));
34 | }
35 |
36 | @RouteConfig(path="/users/login", method=HttpMethod.POST, authRequired=false)
37 | public void login(RoutingContext event) {
38 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.USER);
39 | if (message != null) {
40 | userService.rxGetByEmail(message.getString("email"))
41 | .subscribe((res, ex) -> {
42 | if (ex != null) {
43 | event.fail(new RuntimeException("Invalid user credentials"));
44 | } else {
45 | String hashed = res.getPassword();
46 | if (BCrypt.checkpw(message.getString("password"), hashed)) {
47 | JsonObject authJson = createAuthJson(res);
48 | event.response()
49 | .setStatusCode(HttpResponseStatus.CREATED.code())
50 | .end(Json.encodePrettily(authJson));
51 | } else {
52 | event.fail(new RuntimeException("Invalid user credentials"));
53 | }
54 | }
55 | });
56 | } else {
57 | event.fail(new RuntimeException("Invalid user credentials"));
58 | }
59 |
60 | }
61 |
62 | private static String setPassword(String password) {
63 | String salt = BCrypt.gensalt();
64 | return BCrypt.hashpw(password, salt);
65 | }
66 |
67 | private JsonObject createAuthJson(User user) {
68 | if (user.getId() != null) {
69 | JsonObject authJson = user.toAuthJson();
70 | appendJwt(authJson, user.getId().toHexString());
71 | return new JsonObject().put("user", authJson);
72 | }
73 | return new JsonObject();
74 | }
75 |
76 | @RouteConfig(path="/users", method=HttpMethod.POST, authRequired=false)
77 | public void register(RoutingContext event) {
78 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.USER);
79 | setCreateFields(event, message);
80 | message.put("password", setPassword(message.getString("password")));
81 | userService.rxCreate(message)
82 | .subscribe(res -> handleResponse(event, createAuthJson(res), HttpResponseStatus.CREATED), e -> handleError(event, e));
83 | }
84 |
85 |
86 |
87 | @RouteConfig(path="/user", method = HttpMethod.PUT)
88 | public void put(RoutingContext event) {
89 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.USER);
90 | setUpdateFields(event, message);
91 | userService.rxUpdate(event.get(Constants.USER_ID), message)
92 | .subscribe(res -> handleResponse(event, createAuthJson(res), HttpResponseStatus.OK), e -> handleError(event, e));
93 | }
94 |
95 | @RouteConfig(path="/user")
96 | public void get(RoutingContext event) {
97 | userService.rxGetById(event.get(Constants.USER_ID))
98 | .subscribe(res -> handleResponse(event, createAuthJson(res), HttpResponseStatus.OK), e -> handleError(event, e));
99 | }
100 |
101 | @RouteConfig(path="/profiles/:username", authRequired = false, middlewares = {"extractProfile", "extractUser"})
102 | public void getProfile(RoutingContext event) {
103 | User profile = event.get("profile");
104 | User queryingUser = event.get(Constants.USER);
105 | JsonObject json = new JsonObject().put("profile" ,profile.toProfileJsonFor(queryingUser));
106 | handleResponse(event, json, HttpResponseStatus.OK);
107 | }
108 |
109 | @RouteConfig(path="/profiles/:username/follow", method = HttpMethod.POST, middlewares = {"extractProfile", "extractUser"})
110 | public void follow(RoutingContext event) {
111 |
112 | User profileUser = event.get("profile");
113 | User queryingUser = event.get(Constants.USER);
114 |
115 | queryingUser.follow(profileUser);
116 |
117 | // following is a list of dbrefs. probably this is not the right way to append to it
118 | JsonObject update = new JsonObject().put("following", new JsonObject().put(MorphiaServiceOperator.PUSH, new JsonObject().put("_id", profileUser.getId().toHexString())));
119 | userService.rxUpdate(queryingUser.getId().toHexString(), update)
120 | .map(user -> new JsonObject().put("profile", profileUser.toProfileJsonFor(queryingUser)))
121 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
122 | }
123 |
124 | @RouteConfig(path="/profiles/:username/follow", method = HttpMethod.DELETE, middlewares = {"extractProfile", "extractUser"})
125 | public void unfollow(RoutingContext event) {
126 |
127 | User profileUser = event.get("profile");
128 | User queryingUser = event.get(Constants.USER);
129 |
130 | queryingUser.unfollow(profileUser);
131 | JsonObject update = new JsonObject().put("following", new JsonObject().put(MorphiaServiceOperator.POP, new JsonObject().put("_id", profileUser.getId().toHexString())));
132 | userService.rxUpdate(queryingUser.getId().toHexString(), update)
133 | .map(user -> new JsonObject().put("profile", profileUser.toProfileJsonFor(queryingUser)))
134 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/conduit/src/test/java/routes/QueryTest.java:
--------------------------------------------------------------------------------
1 | package routes;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.codegen.annotations.Nullable;
5 | import io.vertx.conduit.handlers.Constants;
6 | import io.vertx.core.json.JsonArray;
7 | import io.vertx.core.json.JsonObject;
8 | import io.vertx.ext.unit.Async;
9 | import io.vertx.ext.unit.TestContext;
10 | import io.vertx.ext.unit.junit.VertxUnitRunner;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 |
14 | @RunWith(VertxUnitRunner.class)
15 | public class QueryTest extends TestBase {
16 |
17 | @Test(timeout = 120000)
18 | public void testQuery(TestContext tc) {
19 | cleanupUser(tc);
20 | cleanupArticles(tc);
21 | registerUser(tc, user1);
22 | registerUser(tc, user2);
23 | loginUser(tc, user1);
24 |
25 | createArticle(tc, user1, testArticle1);
26 | createArticle(tc, user1, testArticle2);
27 |
28 | loginUser(tc, user2);
29 |
30 | Async favoriteArticle = tc.async();
31 |
32 | webClient.post(PORT, "localhost", "/api/articles/first-article/favorite")
33 | .putHeader(CONTENT_TYPE, JSON)
34 | .putHeader(AUTHORIZATION, getJwt(tc))
35 | .send(ar -> {
36 | if (ar.succeeded()) {
37 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
38 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.ARTICLE);
39 | tc.assertNotNull(json);
40 | JsonObject expected = testArticle1.toJsonFor(user2);
41 | expected.put("id", json.getString("id"));
42 | expected.put("version", json.getLong("version"));
43 | expected.put("favoritesCount", 1);
44 | expected.put("favorited", true);
45 | expected.put("createdAt", json.getString("createdAt"));
46 | expected.put("createUser", json.getString("createUser"));
47 | expected.put("updatedAt", json.getString("updatedAt"));
48 | tc.assertEquals(expected, json);
49 | favoriteArticle.complete();
50 | } else {
51 | tc.fail();
52 | }
53 | });
54 |
55 | favoriteArticle.awaitSuccess();
56 |
57 | Async tagsQuery = tc.async();
58 |
59 | webClient.get(PORT, "localhost", "/api/tags/")
60 | .putHeader(CONTENT_TYPE, JSON)
61 | .putHeader(AUTHORIZATION, getJwt(tc))
62 | .send(
63 | ar -> {
64 | if (ar.succeeded()) {
65 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
66 | JsonArray json = ar.result().bodyAsJsonObject().getJsonArray("tags");
67 | JsonArray expected = new JsonArray().add("food").add("travel").add("dance").add("music");
68 | tc.assertNotNull(json);
69 | tc.assertEquals(expected, json);
70 | tagsQuery.complete();
71 | } else {
72 | tc.fail();
73 | }
74 | });
75 |
76 | tagsQuery.awaitSuccess();
77 |
78 | Async articleQuery = tc.async();
79 |
80 | webClient.get(PORT, "localhost", "/api/articles")
81 | .putHeader(CONTENT_TYPE, JSON)
82 | .putHeader(AUTHORIZATION, getJwt(tc))
83 | .sendJsonObject(new JsonObject().put("query", new JsonObject()
84 | .put("limit", 10)
85 | .put("offset", 0)
86 | .put("author", "user1")
87 | .put("favoriter", "user2")
88 | .put("tags", new JsonArray().add("food").add("music"))
89 | ),
90 | ar -> {
91 | if (ar.succeeded()) {
92 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
93 | @Nullable JsonObject res = ar.result().bodyAsJsonObject();
94 | int count = res.getInteger("articlesCount");
95 | tc.assertEquals(1, count);
96 |
97 | JsonArray articles = res.getJsonArray("articles");
98 | tc.assertEquals(1, articles.size());
99 |
100 | tc.assertEquals(testArticle1.getId().toHexString(), articles.getJsonObject(0).getString("id"));
101 | articleQuery.complete();
102 | } else {
103 | tc.fail();
104 | }
105 | });
106 |
107 | articleQuery.awaitSuccess();
108 |
109 | Async follow = tc.async();
110 |
111 | webClient.post(PORT, "localhost", "/api/profiles/user1/follow")
112 | .putHeader(CONTENT_TYPE, JSON)
113 | .putHeader(AUTHORIZATION, getJwt(tc))
114 | .send(
115 | ar -> {
116 | if (ar.succeeded()) {
117 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
118 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject("profile");
119 | JsonObject jsonExpected = user1.toProfileJsonFor(user2);
120 | jsonExpected.put("following", true);
121 |
122 | tc.assertEquals(jsonExpected, json);
123 | follow.complete();
124 | } else {
125 | tc.fail(ar.cause());
126 | }
127 | });
128 |
129 | follow.awaitSuccess();
130 |
131 | Async feedQuery = tc.async();
132 |
133 | webClient.get(PORT, "localhost", "/api/articles/feed")
134 | .putHeader(CONTENT_TYPE, JSON)
135 | .putHeader(AUTHORIZATION, getJwt(tc))
136 | .sendJsonObject(new JsonObject().put("query", new JsonObject()
137 | .put("limit", 10)
138 | .put("offset", 0)
139 | .put("author", "user1")
140 | .put("favoriter", "user2")
141 | .put("tags", new JsonArray().add("food").add("music"))
142 | ),
143 | ar -> {
144 | if (ar.succeeded()) {
145 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
146 | @Nullable JsonObject res = ar.result().bodyAsJsonObject();
147 | int count = res.getInteger("articlesCount");
148 | tc.assertEquals(2, count);
149 |
150 | JsonArray articles = res.getJsonArray("articles");
151 | tc.assertEquals(2, articles.size());
152 |
153 | // ordered by create time in descending order
154 | tc.assertEquals(testArticle2.getId().toHexString(), articles.getJsonObject(0).getString("id"));
155 | tc.assertEquals(testArticle1.getId().toHexString(), articles.getJsonObject(1).getString("id"));
156 | feedQuery.complete();
157 | } else {
158 | tc.fail();
159 | }
160 | });
161 |
162 | feedQuery.awaitSuccess();
163 |
164 | cleanupArticles(tc);
165 | cleanupUser(tc);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/conduit/src/test/java/routes/UserTest.java:
--------------------------------------------------------------------------------
1 | package routes;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.conduit.handlers.Constants;
5 | import io.vertx.core.json.JsonObject;
6 | import io.vertx.ext.unit.Async;
7 | import io.vertx.ext.unit.TestContext;
8 | import io.vertx.ext.unit.junit.VertxUnitRunner;
9 | import org.junit.After;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 |
13 | @RunWith(VertxUnitRunner.class)
14 | public class UserTest extends TestBase {
15 |
16 | @Test(timeout = TIMEOUT)
17 | public void testRegisterUser(TestContext tc) {
18 | cleanupUser(tc);
19 |
20 | registerUser(tc, user1);
21 |
22 | Async createDupe = tc.async();
23 |
24 | webClient.post(PORT, "localhost", "/api/users")
25 | .putHeader(CONTENT_TYPE, JSON)
26 | .sendJsonObject(new JsonObject()
27 | .put(Constants.USER, user1.toJson()
28 | ), ar -> {
29 | if (ar.succeeded()) {
30 | String body = ar.result().bodyAsString();
31 | tc.assertEquals("Internal Server Error", body);
32 | createDupe.complete();
33 | } else {
34 | tc.fail();
35 | }
36 | });
37 |
38 | createDupe.awaitSuccess();
39 | }
40 |
41 | @Test(timeout = 120000)
42 | public void testUpdateUser(TestContext tc) {
43 | cleanupUser(tc);
44 | registerUser(tc, user1);
45 | loginUser(tc, user1);
46 |
47 | Async update = tc.async();
48 |
49 | webClient.put(PORT, "localhost", "/api/user")
50 | .putHeader(CONTENT_TYPE, JSON)
51 | .putHeader(AUTHORIZATION, getJwt(tc))
52 | .sendJsonObject(new JsonObject().put(Constants.USER, new JsonObject()
53 | .put("bio", "updatedBio")),
54 | ar -> {
55 | if (ar.succeeded()) {
56 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
57 | JsonObject returnedUser = ar.result().bodyAsJsonObject().getJsonObject("user");
58 | tc.assertEquals(user1.getUsername(), returnedUser.getString("username"));
59 | tc.assertEquals(user1.getEmail(), returnedUser.getString("email"));
60 | tc.assertEquals("updatedBio", returnedUser.getString("bio"));
61 | tc.assertNull(returnedUser.getString("image"));
62 | update.complete();
63 | } else {
64 | tc.fail(ar.cause());
65 | }
66 | });
67 | }
68 |
69 | @Test(timeout = TIMEOUT)
70 | public void testGetUser(TestContext tc) {
71 | cleanupUser(tc);
72 | registerUser(tc, user1);
73 | loginUser(tc, user1);
74 |
75 | Async get = tc.async();
76 |
77 | webClient.get(PORT, "localhost", "/api/user")
78 | .putHeader(CONTENT_TYPE, JSON)
79 | .putHeader(AUTHORIZATION, getJwt(tc))
80 | .send(
81 | ar -> {
82 | if (ar.succeeded()) {
83 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
84 | JsonObject returnedUser = ar.result().bodyAsJsonObject().getJsonObject("user");
85 | JsonObject authJson = user1.toAuthJson();
86 | authJson.put("token", tc.get("jwt").toString());
87 | tc.assertEquals(authJson, returnedUser);
88 | get.complete();
89 | } else {
90 | tc.fail(ar.cause());
91 | }
92 | });
93 | }
94 |
95 | @Test(timeout = TIMEOUT)
96 | public void testGetProfile(TestContext tc) {
97 | cleanupUser(tc);
98 | registerUser(tc, user1);
99 |
100 | Async getProfileNoLogin = tc.async();
101 |
102 | webClient.get(PORT, "localhost", "/api/profiles/user1")
103 | .putHeader(CONTENT_TYPE, JSON)
104 | .send(
105 | ar -> {
106 | if (ar.succeeded()) {
107 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
108 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject("profile");
109 | tc.assertEquals(user1.toProfileJsonFor(user1), json);
110 | getProfileNoLogin.complete();
111 | } else {
112 | tc.fail(ar.cause());
113 | }
114 | });
115 |
116 | getProfileNoLogin.awaitSuccess();
117 |
118 | loginUser(tc, user1);
119 |
120 | Async getProfileWithLogin = tc.async();
121 |
122 | webClient.get(PORT, "localhost", "/api/profiles/user1")
123 | .putHeader(CONTENT_TYPE, JSON)
124 | .putHeader(AUTHORIZATION, getJwt(tc))
125 | .send(
126 | ar -> {
127 | if (ar.succeeded()) {
128 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
129 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject("profile");
130 | tc.assertEquals(user1.toProfileJsonFor(user1), json);
131 | getProfileWithLogin.complete();
132 | } else {
133 | tc.fail(ar.cause());
134 | }
135 | });
136 | }
137 |
138 | @Test(timeout = TIMEOUT)
139 | public void testFollow(TestContext tc) {
140 | cleanupUser(tc);
141 | registerUser(tc, user1);
142 | registerUser(tc, user2);
143 | loginUser(tc, user1);
144 |
145 | Async follow = tc.async();
146 |
147 | webClient.post(PORT, "localhost", "/api/profiles/user2/follow")
148 | .putHeader(CONTENT_TYPE, JSON)
149 | .putHeader(AUTHORIZATION, getJwt(tc))
150 | .send(
151 | ar -> {
152 | if (ar.succeeded()) {
153 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
154 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject("profile");
155 | JsonObject jsonExpected = user2.toProfileJsonFor(user1);
156 | jsonExpected.put("following", true);
157 |
158 | tc.assertEquals(jsonExpected, json);
159 | follow.complete();
160 | } else {
161 | tc.fail(ar.cause());
162 | }
163 | });
164 |
165 | follow.awaitSuccess();
166 |
167 | Async unfollow = tc.async();
168 |
169 | webClient.delete(PORT, "localhost", "/api/profiles/user2/follow")
170 | .putHeader(CONTENT_TYPE, JSON)
171 | .putHeader(AUTHORIZATION, getJwt(tc))
172 | .send(
173 | ar -> {
174 | if (ar.succeeded()) {
175 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
176 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject("profile");
177 | JsonObject jsonExpected = user2.toProfileJsonFor(user1);
178 | jsonExpected.put("following", false);
179 |
180 | tc.assertEquals(jsonExpected, json);
181 | unfollow.complete();
182 | } else {
183 | tc.fail(ar.cause());
184 | }
185 | });
186 |
187 | unfollow.awaitSuccess();
188 | }
189 |
190 | @After
191 | public void tearDown(TestContext tc) {
192 | cleanupUser(tc);
193 | super.tearDown(tc);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/conduit/src/test/java/routes/ArticleTest.java:
--------------------------------------------------------------------------------
1 | package routes;
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus;
4 | import io.vertx.conduit.handlers.Constants;
5 | import io.vertx.core.json.JsonArray;
6 | import io.vertx.core.json.JsonObject;
7 | import io.vertx.ext.unit.Async;
8 | import io.vertx.ext.unit.TestContext;
9 | import io.vertx.ext.unit.junit.VertxUnitRunner;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 |
13 | @RunWith(VertxUnitRunner.class)
14 | public class ArticleTest extends TestBase {
15 |
16 | @Test(timeout = TIMEOUT)
17 | public void testCreateArticle(TestContext tc) {
18 | cleanupUser(tc);
19 | cleanupArticles(tc);
20 | registerUser(tc, user1);
21 | loginUser(tc, user1);
22 |
23 | createArticle(tc, user1, testArticle1);
24 |
25 | Async updateArticle = tc.async();
26 |
27 | // Must include the forward slash at the end
28 |
29 | webClient.put(PORT, "localhost", "/api/articles/first-article")
30 | .putHeader(CONTENT_TYPE, JSON)
31 | .putHeader(AUTHORIZATION, getJwt(tc))
32 | .sendJsonObject(new JsonObject()
33 | .put(Constants.ARTICLE, new JsonObject().put("body", "updatedBody")
34 | ), ar -> {
35 | if (ar.succeeded()) {
36 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
37 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.ARTICLE);
38 | tc.assertNotNull(json);
39 | JsonObject expected = testArticle1.toJsonFor(user1);
40 | expected.put("id", json.getString("id"));
41 | expected.put("version", json.getLong("version"));
42 | expected.put("body", "updatedBody");
43 | expected.put("createdAt", json.getString("createdAt"));
44 | expected.put("createUser", json.getString("createUser"));
45 | expected.put("updatedAt", json.getString("updatedAt"));
46 | expected.put("updateUser", json.getString("updateUser"));
47 | tc.assertEquals(expected, json);
48 | updateArticle.complete();
49 | } else {
50 | tc.fail();
51 | }
52 | });
53 |
54 | updateArticle.awaitSuccess();
55 |
56 | Async favoriteArticle = tc.async();
57 |
58 | webClient.post(PORT, "localhost", "/api/articles/first-article/favorite")
59 | .putHeader(CONTENT_TYPE, JSON)
60 | .putHeader(AUTHORIZATION, getJwt(tc))
61 | .send(ar -> {
62 | if (ar.succeeded()) {
63 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
64 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.ARTICLE);
65 | tc.assertNotNull(json);
66 | JsonObject expected = testArticle1.toJsonFor(user1);
67 | expected.put("id", json.getString("id"));
68 | expected.put("version", json.getLong("version"));
69 | expected.put("body", "updatedBody");
70 | expected.put("favoritesCount", 1);
71 | expected.put("favorited", true);
72 | expected.put("createdAt", json.getString("createdAt"));
73 | expected.put("createUser", json.getString("createUser"));
74 | expected.put("updatedAt", json.getString("updatedAt"));
75 | expected.put("updateUser", json.getString("updateUser"));
76 | tc.assertEquals(expected, json);
77 | favoriteArticle.complete();
78 | } else {
79 | tc.fail();
80 | }
81 | });
82 |
83 | favoriteArticle.awaitSuccess();
84 |
85 | Async unfavoriteArticle = tc.async();
86 |
87 | webClient.delete(PORT, "localhost", "/api/articles/first-article/favorite")
88 | .putHeader(CONTENT_TYPE, JSON)
89 | .putHeader(AUTHORIZATION, getJwt(tc))
90 | .send(ar -> {
91 | if (ar.succeeded()) {
92 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
93 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.ARTICLE);
94 | tc.assertNotNull(json);
95 | JsonObject expected = testArticle1.toJsonFor(user1);
96 | expected.put("id", json.getString("id"));
97 | expected.put("version", json.getLong("version"));
98 | expected.put("body", "updatedBody");
99 | expected.put("favoritesCount", 0);
100 | expected.put("favorited", false);
101 | expected.put("createdAt", json.getString("createdAt"));
102 | expected.put("createUser", json.getString("createUser"));
103 | expected.put("updatedAt", json.getString("updatedAt"));
104 | expected.put("updateUser", json.getString("updateUser"));
105 | tc.assertEquals(expected, json);
106 | unfavoriteArticle.complete();
107 | } else {
108 | tc.fail();
109 | }
110 | });
111 |
112 | unfavoriteArticle.awaitSuccess();
113 |
114 | Async createComment = tc.async();
115 |
116 | webClient.post(PORT, "localhost", "/api/articles/first-article/comments")
117 | .putHeader(CONTENT_TYPE, JSON)
118 | .putHeader(AUTHORIZATION, getJwt(tc))
119 | .sendJsonObject(new JsonObject().put("comment", new JsonObject().put("body", "a comment")),
120 | ar -> {
121 | if (ar.succeeded()) {
122 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
123 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.COMMENT);
124 | tc.assertNotNull(json);
125 | tc.put("commentId", json.getString("id"));
126 | tc.assertEquals(testArticle1.getId().toHexString(), json.getString("article"));
127 | tc.assertEquals(testArticle1.getAuthor().getUsername(), json.getJsonObject("author").getString("username"));
128 | tc.assertEquals("a comment", json.getString("body"));
129 | createComment.complete();
130 | } else {
131 | tc.fail();
132 | }
133 | });
134 |
135 | createComment.awaitSuccess();
136 |
137 | Async getComment = tc.async();
138 |
139 | webClient.get(PORT, "localhost", "/api/articles/first-article/comments")
140 | .putHeader(CONTENT_TYPE, JSON)
141 | .putHeader(AUTHORIZATION, getJwt(tc))
142 | .sendJsonObject(new JsonObject().put("comment", new JsonObject().put("body", "a comment")),
143 | ar -> {
144 | if (ar.succeeded()) {
145 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
146 | JsonArray json = ar.result().bodyAsJsonObject().getJsonArray("comments");
147 | tc.assertNotNull(json);
148 | tc.assertEquals(1, json.size());
149 | tc.assertEquals("a comment", json.getJsonObject(0).getString("body"));
150 | getComment.complete();
151 | } else {
152 | tc.fail();
153 | }
154 | });
155 |
156 | getComment.awaitSuccess();
157 |
158 | Async deleteComment = tc.async();
159 |
160 | webClient.delete(PORT, "localhost", "/api/articles/first-article/comments/" + tc.get("commentId"))
161 | .putHeader(CONTENT_TYPE, JSON)
162 | .putHeader(AUTHORIZATION, getJwt(tc))
163 | .send(
164 | ar -> {
165 | if (ar.succeeded()) {
166 | tc.assertEquals(HttpResponseStatus.OK.code(), ar.result().statusCode());
167 | deleteComment.complete();
168 | } else {
169 | tc.fail();
170 | }
171 | });
172 |
173 | deleteComment.awaitSuccess();
174 |
175 | cleanupArticles(tc);
176 | cleanupUser(tc);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/conduit/src/test/java/services/MorphiaServiceTest.java:
--------------------------------------------------------------------------------
1 | package services;
2 |
3 | import io.vertx.conduit.entities.Article;
4 | import io.vertx.conduit.entities.User;
5 | import io.vertx.conduit.services.MorphiaServiceImpl;
6 | import io.vertx.conduit.services.reactivex.MorphiaService;
7 | import io.vertx.core.Vertx;
8 | import io.vertx.core.json.JsonObject;
9 | import io.vertx.core.logging.Logger;
10 | import io.vertx.ext.unit.Async;
11 | import io.vertx.ext.unit.TestContext;
12 | import io.vertx.ext.unit.junit.VertxUnitRunner;
13 | import logging.ContextLogger;
14 | import org.bson.types.ObjectId;
15 | import org.junit.After;
16 | import org.junit.Before;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 |
20 | import static org.junit.Assert.*;
21 |
22 | @RunWith(VertxUnitRunner.class)
23 | public class MorphiaServiceTest {
24 |
25 | private Vertx vertx;
26 | private MorphiaService morphiaService;
27 |
28 | @Before
29 | public void setup(TestContext tc) {
30 | this.vertx = Vertx.vertx();
31 | JsonObject config = new JsonObject().put("db_name", "conduit_test")
32 | .put("host", "localhost").put("port", 27017);
33 | MorphiaServiceImpl delegate = new MorphiaServiceImpl(vertx, config, tc.asyncAssertSuccess());
34 | this.morphiaService = new io.vertx.conduit.services.reactivex.MorphiaService(delegate);
35 | }
36 |
37 | @Test
38 | public void UserCRUD(TestContext tc) throws InterruptedException {
39 | Async async = tc.async();
40 |
41 | User user = new User();
42 | user.setPassword("123");
43 | user.setBio("abc");
44 | user.setEmail("1@2.com");
45 | user.setUsername("xyz");
46 |
47 | this.morphiaService.rxCreateUser(user)
48 | .flatMap(id -> {
49 | user.setId(new ObjectId(id));
50 | return this.morphiaService.rxGetUser(new JsonObject().put("_id", user.getId().toHexString()));
51 | }).flatMap(users -> {
52 | assertEquals(1, users.size());
53 | assertEquals(user.getUsername(), users.get(0).getUsername());
54 | user.setBio("bio_updated");
55 | return this.morphiaService.rxUpdateUser(new JsonObject().put("_id", user.getId().toHexString()), new JsonObject().put("bio", user.getBio()));
56 | }).flatMap(users -> {
57 | assertEquals(1, users.size());
58 | assertEquals(user.getBio(), users.get(0).getBio());
59 | return this.morphiaService.rxDeleteUser(new JsonObject().put("_id", user.getId().toHexString()));
60 | }).subscribe((numDeleted, e) -> {
61 | if (e == null) {
62 | assertEquals(1, (long) numDeleted);
63 | async.complete();
64 | } else {
65 | tc.fail(e);
66 | }
67 | });
68 | }
69 |
70 | @Test
71 | public void userInvalid(TestContext tc) {
72 | Async async1 = tc.async();
73 | Async async2 = tc.async();
74 | Async async3 = tc.async();
75 | Async async4 = tc.async();
76 |
77 | User user = new User();
78 | user.setPassword("123");
79 | user.setBio("abc");
80 |
81 | this.morphiaService.rxCreateUser(user)
82 | .subscribe((id, e) -> {
83 | if (e == null) {
84 | tc.fail("User should not be created");
85 | } else {
86 | async1.complete();
87 | }
88 | });
89 |
90 | user.setUsername("xyz");
91 | this.morphiaService.rxCreateUser(user)
92 | .subscribe((id, e) -> {
93 | if (e == null) {
94 | tc.fail("User should not be created");
95 | } else {
96 | async2.complete();
97 | }
98 | });
99 |
100 | user.setEmail("123");
101 | this.morphiaService.rxCreateUser(user)
102 | .subscribe((id, e) -> {
103 | if (e == null) {
104 | tc.fail("User should not be created");
105 | } else {
106 | async3.complete();
107 | }
108 | });
109 |
110 | user.setEmail("123@xyz.com");
111 | user.setUsername("#*$");
112 | this.morphiaService.rxCreateUser(user)
113 | .subscribe((id, e) -> {
114 | if (e == null) {
115 | tc.fail("User should not be created");
116 | } else {
117 | async4.complete();
118 | }
119 | });
120 | }
121 |
122 | @Test
123 | public void userDupe(TestContext tc) throws InterruptedException {
124 | Async async1 = tc.async();
125 | Async async2 = tc.async();
126 | Async async3 = tc.async();
127 | Async async4 = tc.async();
128 |
129 | User user = new User();
130 | user.setPassword("123");
131 | user.setBio("abc");
132 | user.setEmail("1@2.com");
133 | user.setUsername("xyz");
134 |
135 | User user2 = new User();
136 | user2.setPassword("123");
137 | user2.setBio("abc");
138 | user2.setEmail("3@4.com");
139 | user2.setUsername("xyz");
140 |
141 | User user3 = new User();
142 | user3.setPassword("123");
143 | user3.setBio("abc");
144 | user3.setEmail("1@2.com");
145 | user3.setUsername("xyz2");
146 |
147 | this.morphiaService.rxCreateUser(user)
148 | .subscribe((id, e) -> {
149 | if (e == null) {
150 | user.setId(new ObjectId(id));
151 | async1.complete();
152 | } else {
153 | tc.fail(e);
154 | }
155 | });
156 |
157 | async1.await();
158 | this.morphiaService.rxCreateUser(user2)
159 | .subscribe((id, e) -> {
160 | if (e == null) {
161 | tc.fail("User2 shouldn't be written");
162 | }
163 | async2.complete();
164 | });
165 |
166 | async2.await();
167 |
168 | this.morphiaService.rxCreateUser(user3)
169 | .subscribe((id, e) -> {
170 | if (e == null) {
171 | tc.fail("User3 shouldn't be written");
172 | }
173 | async3.complete();
174 | });
175 |
176 | async3.await();
177 |
178 | this.morphiaService.rxDeleteUser(new JsonObject().put("_id", user.getId().toHexString()))
179 | .subscribe((numDeleted, e) -> {
180 | if (e == null) {
181 | assertEquals(1, (long) numDeleted);
182 | async4.complete();
183 | } else {
184 | tc.fail(e);
185 | }
186 | });
187 | }
188 |
189 | @Test
190 | public void ArticleCRUD(TestContext tc) {
191 | Async async = tc.async();
192 |
193 | User user = new User();
194 | user.setPassword("123");
195 | user.setBio("abc");
196 | user.setEmail("1@2.com");
197 | user.setUsername("xyz");
198 |
199 | Article article = new Article();
200 | article.setAuthor(user);
201 | article.setTitle("Test article");
202 | article.setSlug("Test-article");
203 | article.setBody("No body no body");
204 |
205 | // have to store the user first and assign it an id for the @Reference annotation to work
206 |
207 | this.morphiaService.rxCreateUser(user)
208 | .flatMap(id -> {
209 | user.setId(new ObjectId(id));
210 | return this.morphiaService.rxCreateArticle(article);
211 | }).flatMap(id -> {
212 | article.setId(new ObjectId(id));
213 | return this.morphiaService.rxGetArticle(new JsonObject().put("_id", article.getId().toHexString()));
214 | }).flatMap(articles -> {
215 | assertEquals(1, articles.size());
216 | assertEquals(article.getSlug(), articles.get(0).getSlug());
217 | assertNotNull(article.getAuthor());
218 | assertEquals(user.getUsername(), article.getAuthor().getUsername());
219 | article.setBody("but you~");
220 | return this.morphiaService.rxUpdateArticle(new JsonObject().put("_id", article.getId().toHexString()), new JsonObject().put("body", article.getBody()));
221 | }).flatMap(articles -> {
222 | assertEquals(1, articles.size());
223 | assertEquals(article.getBody(), articles.get(0).getBody());
224 | return this.morphiaService.rxDeleteArticle(new JsonObject().put("_id", article.getId().toHexString()));
225 | }).flatMap(numDeleted -> {
226 | assertEquals(1, (long) numDeleted);
227 | return this.morphiaService.rxDeleteUser(new JsonObject().put("_id", user.getId().toHexString()));
228 | }).subscribe((numDeleted, e) -> {
229 | if (e == null) {
230 | assertEquals(1, (long) numDeleted);
231 | async.complete();
232 | } else {
233 | tc.fail(e);
234 | }
235 | });
236 | }
237 |
238 |
239 | @After
240 | public void tearDown(TestContext tc) {
241 | vertx.setTimer(1000, t -> { System.out.println("timer complete"); });
242 | vertx.close(tc.asyncAssertSuccess());
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/conduit/src/test/java/routes/TestBase.java:
--------------------------------------------------------------------------------
1 | package routes;
2 |
3 |
4 | import io.netty.handler.codec.http.HttpResponseStatus;
5 | import io.vertx.conduit.entities.Article;
6 | import io.vertx.conduit.entities.User;
7 | import io.vertx.conduit.handlers.Constants;
8 | import io.vertx.conduit.services.ArticleService;
9 | import io.vertx.conduit.services.CommentService;
10 | import io.vertx.conduit.services.UserService;
11 | import io.vertx.conduit.verticles.*;
12 | import io.vertx.core.DeploymentOptions;
13 | import io.vertx.core.Vertx;
14 | import io.vertx.core.json.JsonArray;
15 | import io.vertx.core.json.JsonObject;
16 | import io.vertx.ext.unit.Async;
17 | import io.vertx.ext.unit.TestContext;
18 | import io.vertx.ext.unit.junit.VertxUnitRunner;
19 | import io.vertx.ext.web.client.WebClient;
20 | import io.vertx.serviceproxy.ServiceProxyBuilder;
21 | import org.bson.types.ObjectId;
22 | import org.junit.After;
23 | import org.junit.Before;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 |
27 | @RunWith(VertxUnitRunner.class)
28 | public class TestBase {
29 |
30 | static {
31 | System.getProperties().setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.Log4j2LogDelegateFactory");
32 | }
33 |
34 | protected final static int TIMEOUT = 5000;
35 | protected static final String CONTENT_TYPE = "Content-Type";
36 | protected static final String JSON = "application/json";
37 | protected static final String AUTHORIZATION = "Authorization";
38 | protected static int PORT = 3000;
39 |
40 | protected Vertx vertx;
41 |
42 | protected WebClient webClient;
43 |
44 | protected User user1;
45 |
46 | protected User user2;
47 |
48 | protected Article testArticle1;
49 |
50 | protected Article testArticle2;
51 |
52 | protected io.vertx.conduit.services.reactivex.UserService userService;
53 |
54 | protected io.vertx.conduit.services.reactivex.ArticleService articleService;
55 |
56 | protected io.vertx.conduit.services.reactivex.CommentService commentService;
57 |
58 | @Before
59 | public void setup(TestContext tc) {
60 |
61 | vertx = Vertx.vertx();
62 |
63 | webClient = WebClient.create(vertx);
64 |
65 | DeploymentOptions options = new DeploymentOptions()
66 | .setConfig(new JsonObject()
67 | .put("secret", "testsecret")
68 | .put("mongodb", new JsonObject().put("db_name", "conduit_test")
69 | .put("host", "localhost")
70 | .put("port", 27017))
71 | );
72 |
73 | JsonObject user1Json = new JsonObject().put("username", "user1").put("password", "123").put("email", "user1@t.com").put("bio", "I am not a robot");
74 | JsonObject user2Json = new JsonObject().put("username", "user2").put("password", "456").put("email", "user2@t.com").put("bio", "I work 24/7");
75 |
76 | user1 = new User(user1Json);
77 | user2 = new User(user2Json);
78 |
79 | JsonObject article1Json = new JsonObject().put("title", "first article").put("slug", "first-article").put("description", "We have to test it out.").put("body", "Not much to say").put("comments", new JsonArray()).put("tagList", new JsonArray().add("food").add("travel"));
80 | JsonObject article2Json = new JsonObject().put("title", "second article").put("slug", "second-article").put("description", "Last article is really funny").put("body", "It's not over").put("comments", new JsonArray()).put("tagList", new JsonArray().add("music").add("dance"));
81 |
82 | testArticle1 = new Article(article1Json);
83 | testArticle2 = new Article(article2Json);
84 |
85 | testArticle1.setAuthor(user1);
86 | testArticle2.setAuthor(user1);
87 |
88 | vertx.deployVerticle(ArticleServiceVerticle.class.getName(), tc.asyncAssertSuccess());
89 | vertx.deployVerticle(HttpVerticle.class.getName(), options, tc.asyncAssertSuccess());
90 | vertx.deployVerticle(MorphiaServiceVerticle.class.getName(), options, tc.asyncAssertSuccess());
91 | vertx.deployVerticle(UserServiceVerticle.class.getName(), options, tc.asyncAssertSuccess());
92 | vertx.deployVerticle(CommentServiceVerticle.class.getName(), options, tc.asyncAssertSuccess());
93 |
94 | {
95 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(UserService.ADDRESS);
96 | UserService delegate = builder.build(UserService.class);
97 | this.userService = new io.vertx.conduit.services.reactivex.UserService(delegate);
98 | }
99 |
100 | {
101 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(ArticleService.ADDRESS);
102 | ArticleService delegate = builder.build(ArticleService.class);
103 | this.articleService = new io.vertx.conduit.services.reactivex.ArticleService(delegate);
104 | }
105 |
106 | {
107 | ServiceProxyBuilder builder = new ServiceProxyBuilder(vertx).setAddress(CommentService.ADDRESS);
108 | CommentService delegate = builder.build(CommentService.class);
109 | this.commentService = new io.vertx.conduit.services.reactivex.CommentService(delegate);
110 | }
111 | }
112 |
113 | protected void cleanupUser(TestContext tc) {
114 | Async cleanup = tc.async();
115 | userService.rxDeleteByUsername(user1.getUsername())
116 | .flatMap(ignored -> userService.rxDeleteByUsername(user2.getUsername()))
117 | .subscribe((deleteCount, ex) -> {
118 | cleanup.complete();
119 | });
120 |
121 | cleanup.awaitSuccess();
122 | }
123 |
124 | protected void cleanupArticles(TestContext tc) {
125 | Async cleanup = tc.async();
126 | articleService.rxDelete(testArticle1.getSlug())
127 | .flatMap(ignored -> articleService.rxDelete(testArticle2.getSlug()))
128 | .subscribe((deleteCount, ex) -> {
129 | cleanup.complete();
130 | });
131 |
132 | cleanup.awaitSuccess();
133 | }
134 |
135 | protected void registerUser(TestContext tc, User user) {
136 | Async create = tc.async();
137 |
138 | webClient.post(PORT, "localhost", "/api/users")
139 | .putHeader(CONTENT_TYPE, JSON)
140 | .sendJsonObject(new JsonObject()
141 | .put(Constants.USER, user.toJson()
142 | ), ar -> {
143 | if (ar.succeeded()) {
144 | tc.assertEquals(HttpResponseStatus.CREATED.code(), ar.result().statusCode());
145 | JsonObject returnedUser = ar.result().bodyAsJsonObject().getJsonObject("user");
146 | tc.assertEquals(user.getUsername(), returnedUser.getString("username"));
147 | tc.assertEquals(user.getEmail(), returnedUser.getString("email"));
148 | tc.assertEquals(user.getBio(), returnedUser.getString("bio"));
149 | tc.assertNull(returnedUser.getString("image"));
150 | create.complete();
151 | } else {
152 | tc.fail(ar.cause());
153 | }
154 | });
155 |
156 | create.awaitSuccess();
157 | }
158 |
159 | protected void loginUser(TestContext tc, User user) {
160 | Async login = tc.async();
161 |
162 | webClient.post(PORT, "localhost", "/api/users/login")
163 | .putHeader(CONTENT_TYPE, JSON)
164 | .sendJsonObject(new JsonObject().put(Constants.USER, new JsonObject()
165 | .put("email", user.getEmail())
166 | .put("password", user.getPassword())),
167 | ar -> {
168 | if (ar.succeeded()) {
169 | tc.assertEquals(HttpResponseStatus.CREATED.code(), ar.result().statusCode());
170 | JsonObject returnedUser = ar.result().bodyAsJsonObject().getJsonObject("user");
171 | tc.assertNotNull(returnedUser.getString(Constants.AUTH_KEY));
172 | tc.put("jwt", returnedUser.getString(Constants.AUTH_KEY));
173 | login.complete();
174 | } else {
175 | tc.fail(ar.cause());
176 | }
177 | });
178 |
179 | login.awaitSuccess();
180 | }
181 |
182 | protected void createArticle(TestContext tc, User user, Article article) {
183 | Async createArticle = tc.async();
184 |
185 | // Must include the forward slash at the end
186 |
187 | webClient.post(PORT, "localhost", "/api/articles/")
188 | .putHeader(CONTENT_TYPE, JSON)
189 | .putHeader(AUTHORIZATION, getJwt(tc))
190 | .sendJsonObject(new JsonObject()
191 | .put(Constants.ARTICLE, article.toJson()
192 | ), ar -> {
193 | if (ar.succeeded()) {
194 | tc.assertEquals(HttpResponseStatus.CREATED.code(), ar.result().statusCode());
195 | JsonObject json = ar.result().bodyAsJsonObject().getJsonObject(Constants.ARTICLE);
196 | tc.assertNotNull(json);
197 | JsonObject expected = article.toJsonFor(user);
198 | expected.put("id", json.getString("id"));
199 | expected.put("createdAt", json.getString("createdAt"));
200 | expected.put("createUser", json.getString("createUser"));
201 | expected.put("updatedAt", json.getString("updatedAt"));
202 | tc.assertEquals(expected, json);
203 | article.setId(new ObjectId(json.getString("id")));
204 | createArticle.complete();
205 | } else {
206 | tc.fail();
207 | }
208 | });
209 |
210 | createArticle.awaitSuccess();
211 | }
212 |
213 |
214 | protected static String getJwt(TestContext tc) {
215 | return Constants.AUTH_HEADER + " " + tc.get("jwt").toString();
216 | }
217 |
218 | @Test
219 | public void testSetup(TestContext testContext){
220 | testContext.assertTrue(true);
221 | }
222 |
223 | @After
224 | public void tearDown(TestContext tc) {
225 | vertx.setTimer(1000, t -> { System.out.println("timer complete"); });
226 | vertx.close(tc.asyncAssertSuccess());
227 | }
228 |
229 |
230 | }
231 |
232 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/handlers/ArticleHandler.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.handlers;
2 |
3 | import com.github.slugify.Slugify;
4 | import io.netty.handler.codec.http.HttpResponseStatus;
5 | import io.vertx.conduit.entities.Article;
6 | import io.vertx.conduit.entities.Comment;
7 | import io.vertx.conduit.entities.User;
8 | import io.vertx.conduit.services.MorphiaServiceOperator;
9 | import io.vertx.core.Vertx;
10 | import io.vertx.core.http.HttpMethod;
11 | import io.vertx.core.json.Json;
12 | import io.vertx.core.json.JsonArray;
13 | import io.vertx.core.json.JsonObject;
14 | import io.vertx.ext.web.RoutingContext;
15 | import routerutils.RouteConfig;
16 |
17 | import java.util.Objects;
18 |
19 | import static io.vertx.conduit.handlers.Constants.QUERY;
20 |
21 | @RouteConfig(path="/api/articles", produces = "application/json")
22 | public class ArticleHandler extends ConduitHandler {
23 |
24 | private final Slugify slugify;
25 |
26 | public ArticleHandler(Vertx vertx) {
27 | super(vertx);
28 | this.slugify = new Slugify();
29 | }
30 |
31 | @RouteConfig(path="", method= HttpMethod.POST, middlewares = "extractUser")
32 | public void create(RoutingContext event) {
33 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.ARTICLE);
34 | setCreateFields(event, message);
35 | User user = event.get(Constants.USER);
36 | message.put("slug", slugify.slugify(message.getString("title")));
37 | message.put("author", user.toJson());
38 |
39 | articleService.rxCreate(message)
40 | .map(article -> new JsonObject().put(Constants.ARTICLE, article.toJsonFor(user)))
41 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.CREATED), e -> handleError(event, e));
42 | }
43 |
44 | @RouteConfig(path="", method = HttpMethod.GET, authRequired = false, middlewares = "extractUser")
45 | public void queryArticles(RoutingContext event) {
46 | JsonObject query;
47 | JsonObject bodyAsJson = event.getBodyAsJson();
48 | if (bodyAsJson != null && bodyAsJson.getJsonObject(QUERY) != null) {
49 | query = bodyAsJson.getJsonObject(QUERY);
50 | } else {
51 | query = new JsonObject();
52 | }
53 | User queryingUser = event.get(Constants.USER);
54 |
55 | morphiaService.rxQueryArticles(queryingUser, query)
56 | .doOnError(e -> event.fail(e))
57 | .subscribe(json -> {
58 | event.response()
59 | .setStatusCode(HttpResponseStatus.OK.code())
60 | .end(Json.encodePrettily(json));
61 | });
62 | }
63 |
64 | //this conflicts with /articles/:article
65 | //Put here before specifically to override so /articles/feed/ goes to this method instead of the get method
66 | @RouteConfig(path="/feed", method = HttpMethod.GET, middlewares = "extractUser")
67 | public void queryArticlesFeed(RoutingContext event) {
68 | JsonObject query;
69 | JsonObject bodyAsJson = event.getBodyAsJson();
70 | if (bodyAsJson != null && bodyAsJson.getJsonObject(QUERY) != null) {
71 | query = bodyAsJson.getJsonObject(QUERY);
72 | } else {
73 | query = new JsonObject();
74 | }
75 | User queryingUser = event.get(Constants.USER);
76 |
77 | morphiaService.rxQueryArticlesFeed(queryingUser, query)
78 | .doOnError(e -> event.fail(e))
79 | .subscribe(json -> {
80 | event.response()
81 | .setStatusCode(HttpResponseStatus.OK.code())
82 | .end(Json.encodePrettily(json));
83 | });
84 | }
85 |
86 | @RouteConfig(path="/:article", method=HttpMethod.GET, middlewares = {"extractArticle", "extractUser"})
87 | public void get(RoutingContext event){
88 |
89 | Article article = event.get(Constants.ARTICLE);
90 | event.response()
91 | .setStatusCode(HttpResponseStatus.OK.code())
92 | .end(Json.encodePrettily(new JsonObject().put(Constants.ARTICLE, article.toJsonFor(event.get(Constants.USER)))));
93 | }
94 |
95 | @RouteConfig(path="/:article", method=HttpMethod.PUT, middlewares = {"extractArticle", "extractUser"})
96 | public void update(RoutingContext event){
97 | Article article = event.get(Constants.ARTICLE);
98 | User user = event.get(Constants.USER);
99 |
100 | if (!Objects.equals(user.getId(), article.getAuthor().getId())) {
101 | event.fail(new RuntimeException("Invalid User"));
102 | }
103 |
104 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.ARTICLE);
105 | setUpdateFields(event, message);
106 |
107 | articleService.rxUpdate(article.getSlug(), message)
108 | .map(updatedArticle -> new JsonObject().put(Constants.ARTICLE, updatedArticle.toJsonFor(user)))
109 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
110 | }
111 |
112 | @RouteConfig(path="/:article", method=HttpMethod.DELETE, middlewares = {"extractArticle", "extractUser"})
113 | public void delete(RoutingContext event){
114 | Article article = event.get(Constants.ARTICLE);
115 | if (article.getAuthor().getId() != null && Objects.equals(event.get(Constants.USER_ID), article.getAuthor().getId().toHexString())) {
116 | articleService.rxDelete(article.getSlug()).subscribe((ignored, ex) -> {
117 | if (ex == null) {
118 | event.response()
119 | .setStatusCode(HttpResponseStatus.OK.code())
120 | .end();
121 | } else {
122 | event.fail(ex);
123 | }
124 | });
125 | } else {
126 | event.fail(new RuntimeException("Invalid user"));
127 | }
128 | }
129 |
130 | @RouteConfig(path="/:article/favorite", method=HttpMethod.POST, middlewares = {"extractArticle", "extractUser"})
131 | public void favorite(RoutingContext event) {
132 | Article article = event.get(Constants.ARTICLE);
133 | User user = event.get(Constants.USER);
134 | if (!user.isFavorite(article.getSlug())) {
135 | user.addFavorite(article.getSlug());
136 | } else {
137 | event.response()
138 | .setStatusCode(HttpResponseStatus.OK.code())
139 | .end(Json.encodePrettily(new JsonObject().put(Constants.ARTICLE, article.toJsonFor(user))));
140 | return;
141 | }
142 | JsonObject update = new JsonObject().put("favorites", new JsonObject().put(MorphiaServiceOperator.PUSH, article.getSlug()));
143 |
144 | userService.rxUpdate(user.getId().toHexString(), update)
145 | .flatMap(ignored -> userService.rxGetFavoriteCount(article.getSlug()))
146 | .flatMap(count -> {
147 | article.setFavoritesCount(count);
148 | return articleService.rxUpdate(article.getSlug(), new JsonObject().put("favoritesCount", count));
149 | })
150 | .map(updatedArticle -> new JsonObject().put(Constants.ARTICLE, updatedArticle.toJsonFor(user)))
151 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
152 | }
153 |
154 | @RouteConfig(path="/:article/favorite", method=HttpMethod.DELETE, middlewares = {"extractArticle", "extractUser"})
155 | public void unfavorite(RoutingContext event) {
156 | Article article = event.get(Constants.ARTICLE);
157 | User user = event.get(Constants.USER);
158 | if (user.isFavorite(article.getSlug())) {
159 | user.removeFavorite(article.getSlug());
160 | } else {
161 | event.response()
162 | .setStatusCode(HttpResponseStatus.OK.code())
163 | .end(Json.encodePrettily(article.toJsonFor(user)));
164 | return;
165 | }
166 | JsonObject update = new JsonObject().put("favorites", new JsonObject().put(MorphiaServiceOperator.POP, article.getSlug()));
167 |
168 | userService.rxUpdate(user.getId().toHexString(), update)
169 | .flatMap(ignored -> {
170 | return userService.rxGetFavoriteCount(article.getSlug());
171 | })
172 | .flatMap(count -> {
173 | article.setFavoritesCount(count);
174 | return articleService.rxUpdate(article.getSlug(), new JsonObject().put("favoritesCount", count));
175 | })
176 | .map(updatedArticle -> new JsonObject().put(Constants.ARTICLE, updatedArticle.toJsonFor(user)))
177 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
178 | }
179 |
180 | @RouteConfig(path="/:article/comments", method= HttpMethod.POST, middlewares = {"extractUser", "extractArticle"})
181 | public void createComment(RoutingContext event) {
182 | User user = event.get(Constants.USER);
183 | Article article = event.get(Constants.ARTICLE);
184 |
185 | JsonObject message = event.getBodyAsJson().getJsonObject(Constants.COMMENT);
186 | message.put("author", user.toJson());
187 | message.put(Constants.ARTICLE, article.getId().toHexString());
188 | setCreateFields(event, message);
189 |
190 | commentService.rxCreate(message)
191 | .map(comment -> {
192 | JsonObject update = new JsonObject().put("comments", new JsonObject().put(MorphiaServiceOperator.PUSH, new JsonObject().put("_id", comment.getId().toHexString())));
193 | // FIXME how can we avoid this?
194 | articleService.rxUpdate(article.getSlug(), update).subscribe();
195 | return comment;
196 | })
197 | .map(comment -> new JsonObject().put(Constants.COMMENT, comment.toJsonFor(user)))
198 | .subscribe(res -> handleResponse(event, res, HttpResponseStatus.OK), e -> handleError(event, e));
199 | }
200 |
201 | @RouteConfig(path="/:article/comments", method= HttpMethod.GET, middlewares = {"extractUser", "extractArticle"})
202 | public void getComments(RoutingContext event) {
203 | User user = event.get(Constants.USER);
204 | Article article = event.get(Constants.ARTICLE);
205 |
206 | JsonObject comments = new JsonObject();
207 | JsonArray array = new JsonArray();
208 | if (article.getComments() != null) {
209 | article.getComments().forEach(x -> array.add(x.toJsonFor(user)));
210 | }
211 | comments.put("comments", array);
212 |
213 | event.response()
214 | .setStatusCode(HttpResponseStatus.OK.code())
215 | .end(Json.encodePrettily(comments));
216 | }
217 |
218 | @RouteConfig(path="/:article/comments/:comment", method= HttpMethod.DELETE, middlewares = {"extractUser", "extractComment", "extractArticle"})
219 | public void deleteComment(RoutingContext event) {
220 | User user = event.get(Constants.USER);
221 | Article article = event.get(Constants.ARTICLE);
222 | Comment comment = event.get(Constants.COMMENT);
223 |
224 | if (comment.getAuthor().getId().equals(user.getId())) {
225 | JsonObject update = new JsonObject().put("comments", new JsonObject().put(MorphiaServiceOperator.POP, new JsonObject().put("_id", comment.getId().toHexString())));
226 | articleService.rxUpdate(article.getSlug(), update)
227 | .flatMap(ignored -> commentService.rxDelete(comment.getId().toHexString()))
228 | .subscribe((ignored, ex) -> {
229 | if (ex == null) {
230 | event.response()
231 | .setStatusCode(HttpResponseStatus.OK.code())
232 | .end();
233 | } else {
234 | event.fail(ex);
235 | }
236 | });
237 |
238 | } else {
239 | event.fail(new RuntimeException("Invalid user"));
240 | }
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/conduit/src/main/java/io/vertx/conduit/services/MorphiaServiceImpl.java:
--------------------------------------------------------------------------------
1 | package io.vertx.conduit.services;
2 |
3 | import com.mongodb.MongoClient;
4 | import dev.morphia.Datastore;
5 | import dev.morphia.Key;
6 | import dev.morphia.Morphia;
7 | import dev.morphia.ValidationExtension;
8 | import dev.morphia.query.*;
9 | import io.vertx.conduit.entities.Article;
10 | import io.vertx.conduit.entities.Base;
11 | import io.vertx.conduit.entities.Comment;
12 | import io.vertx.conduit.entities.User;
13 | import io.vertx.core.AsyncResult;
14 | import io.vertx.core.Future;
15 | import io.vertx.core.Handler;
16 | import io.vertx.core.Vertx;
17 | import io.vertx.core.json.JsonArray;
18 | import io.vertx.core.json.JsonObject;
19 | import org.bson.Document;
20 | import org.bson.types.ObjectId;
21 |
22 | import java.util.Iterator;
23 | import java.util.List;
24 | import java.util.Map;
25 | import java.util.stream.Collectors;
26 |
27 | public class MorphiaServiceImpl implements MorphiaService {
28 |
29 | private final Morphia morphia;
30 | private final Vertx vertx;
31 | private final Datastore datastore;
32 | private final MongoClient mongoClient;
33 |
34 | public MorphiaServiceImpl(final Vertx vertx, final JsonObject dbConfig, final Handler> readyHandler) {
35 | this.vertx = vertx;
36 | this.mongoClient = new MongoClient(dbConfig.getString("host"), dbConfig.getInteger("port"));
37 | this.morphia = new Morphia();
38 | this.morphia.map(User.class);
39 | this.morphia.map(Article.class);
40 | this.morphia.map(Comment.class);
41 | this.datastore = morphia.createDatastore(mongoClient, dbConfig.getString("db_name"));
42 |
43 | new ValidationExtension(morphia);
44 |
45 | vertx.executeBlocking(future -> {
46 | try {
47 | mongoClient.getDatabase(dbConfig.getString("db_name")).runCommand(new Document("ping", "1"));
48 | datastore.ensureIndexes();
49 | future.complete();
50 | } catch (Exception e) {
51 | future.fail(e);
52 | }
53 | }, res -> {
54 | if (res.succeeded()) {
55 | readyHandler.handle(Future.succeededFuture(this));
56 | } else {
57 | readyHandler.handle(Future.failedFuture(res.cause()));
58 | }
59 | });
60 | }
61 |
62 | @Override
63 | public void getUser(final JsonObject query, Handler>> resultHandler) {
64 | find(User.class, query, resultHandler);
65 | }
66 |
67 | @Override
68 | public void getArticle(final JsonObject query, Handler>> resultHandler) {
69 | find(Article.class, query, resultHandler);
70 | }
71 |
72 | @Override
73 | public void getComment(JsonObject query, Handler>> resultHandler) {
74 | find(Comment.class, query, resultHandler);
75 | }
76 |
77 | @Override
78 | public void createUser(User entity, final Handler> resultHandler) {
79 | save(entity, resultHandler);
80 | }
81 |
82 | @Override
83 | public void createArticle(Article entity, Handler> resultHandler) {
84 | save(entity, resultHandler);
85 | }
86 |
87 | @Override
88 | public void createComment(Comment entity, Handler> resultHandler) {
89 | save(entity, resultHandler);
90 | }
91 |
92 | @Override
93 | public void updateUser(JsonObject query, JsonObject update, Handler>> resultHandler) {
94 | update(User.class, query, update, resultHandler);
95 | }
96 |
97 | @Override
98 | public void updateArticle(JsonObject query, JsonObject update, Handler>> resultHandler) {
99 | update(Article.class, query, update, resultHandler);
100 | }
101 |
102 | @Override
103 | public void updateComment(JsonObject query, JsonObject update, Handler>> resultHandler) {
104 | update(Comment.class, query, update, resultHandler);
105 | }
106 |
107 | @Override
108 | public void deleteUser(JsonObject query, Handler> resultHandler) {
109 | delete(User.class, query, resultHandler);
110 | }
111 |
112 | @Override
113 | public void deleteArticle(JsonObject query, Handler> resultHandler) {
114 | delete(Article.class, query, resultHandler);
115 | }
116 |
117 | @Override
118 | public void deleteComment(JsonObject query, Handler> resultHandler) {
119 | delete(Comment.class, query, resultHandler);
120 | }
121 |
122 | @Override
123 | public void queryTags(Handler>> resultHandler) {
124 | vertx.executeBlocking(future -> {
125 | future.complete(datastore.getCollection(Article.class).distinct("tagList"));
126 | }, res -> {
127 | if (res.succeeded()) {
128 | resultHandler.handle(Future.succeededFuture((List)res.result()));
129 | } else {
130 | resultHandler.handle(Future.failedFuture(res.cause()));
131 | }
132 | });
133 | }
134 |
135 | @Override
136 | public void queryArticles(User user, JsonObject json, Handler> resultHandler) {
137 | vertx.executeBlocking(future -> {
138 | FindOptions findOptions = new FindOptions()
139 | .limit(json.getInteger("limit", 20))
140 | .skip(json.getInteger("offset", 0));
141 |
142 | Query query = datastore.createQuery(Article.class);
143 | query.order(Sort.descending("createdAt"));
144 |
145 | if (json.getString("author") != null) {
146 | Query userQuery = datastore.createQuery(User.class);
147 | userQuery.field("username").equal(json.getString("author"));
148 | User author = userQuery.first();
149 | query.field("author").equal(author);
150 | }
151 |
152 | if (json.getString("favoriter") != null) {
153 | Query userQuery = datastore.createQuery(User.class);
154 | userQuery.field("username").equal(json.getString("favoriter"));
155 | User favoriter = userQuery.first();
156 | query.field("slug").in(favoriter.getFavorites());
157 | }
158 |
159 | if (json.getJsonArray("tags") != null) {
160 | List criteria = json.getJsonArray("tags").stream().map(tag -> query.criteria("tagList").in(json.getJsonArray("tags"))).collect(Collectors.toList());
161 | query.or(criteria.toArray(new Criteria[0]));
162 | }
163 |
164 | long count = query.count();
165 | List articles = query.find(findOptions).toList();
166 | JsonArray array = new JsonArray();
167 | articles.forEach(article -> array.add(article.toJsonFor(user)));
168 |
169 | JsonObject res = new JsonObject();
170 |
171 | res.put("articlesCount", count);
172 | res.put("articles", array);
173 |
174 | future.complete(res);
175 | }, res -> {
176 | if (res.succeeded()) {
177 | resultHandler.handle(Future.succeededFuture((JsonObject)res.result()));
178 | } else {
179 | resultHandler.handle(Future.failedFuture(res.cause()));
180 | }
181 | });
182 | }
183 |
184 | @Override
185 | public void queryArticlesFeed(User user, JsonObject json, Handler> resultHandler) {
186 | vertx.executeBlocking(future -> {
187 | FindOptions findOptions = new FindOptions()
188 | .limit(json.getInteger("limit", 20))
189 | .skip(json.getInteger("offset", 0));
190 |
191 |
192 | Query query = datastore.createQuery(Article.class);
193 | query.order(Sort.descending("createdAt"));
194 |
195 | if (json.getString("queryingUser") != null) {
196 | Query userQuery = datastore.createQuery(User.class);
197 | userQuery.field("username").equal(json.getString("queryingUser"));
198 | User queryingUser = userQuery.first();
199 | query.field("author").in(queryingUser.getFollowingUsers());
200 | }
201 |
202 | long count = query.count();
203 | List articles = query.find(findOptions).toList();
204 | JsonArray array = new JsonArray();
205 | articles.forEach(article -> array.add(article.toJsonFor(user)));
206 |
207 | JsonObject res = new JsonObject();
208 |
209 | res.put("articlesCount", count);
210 | res.put("articles", array);
211 |
212 | future.complete(res);
213 | }, res -> {
214 | if (res.succeeded()) {
215 | resultHandler.handle(Future.succeededFuture((JsonObject) res.result()));
216 | } else {
217 | resultHandler.handle(Future.failedFuture(res.cause()));
218 | }
219 | });
220 | }
221 |
222 | public void update(final Class clazz, final JsonObject query, final JsonObject update, final Handler>> resultHandler) {
223 | vertx.executeBlocking(future -> {
224 | Query updateQuery = datastore.createQuery(clazz);
225 |
226 | for(Iterator> it = query.iterator(); it.hasNext();) {
227 | Map.Entry pair = it.next();
228 |
229 | if ("_id".equals(pair.getKey())) {
230 | updateQuery.filter("_id", new ObjectId(pair.getValue().toString()));
231 | } else {
232 | updateQuery.filter(pair.getKey(), pair.getValue());
233 | }
234 | }
235 |
236 | final UpdateOperations updateOperations = datastore.createUpdateOperations(clazz);
237 | for(Iterator> it = update.iterator(); it.hasNext();) {
238 | Map.Entry pair = it.next();
239 | if(pair.getValue() instanceof JsonObject) {
240 | String field = pair.getKey();
241 | JsonObject operations = (JsonObject) pair.getValue();
242 | for(Iterator> it2 = operations.iterator(); it2.hasNext(); ) {
243 | Map.Entry operation = it2.next();
244 | String operator = operation.getKey();
245 | Object value = operation.getValue();
246 | if (value instanceof JsonObject) {
247 | JsonObject valueJson = (JsonObject) value;
248 | if (valueJson.size() == 1 && valueJson.getString("_id") != null) {
249 | value = new ObjectId(valueJson.getString("_id"));
250 | }
251 | }
252 |
253 | if (MorphiaServiceOperator.PUSH.equals(operator)) {
254 | updateOperations.addToSet(field, value);
255 | } else if (MorphiaServiceOperator.POP.equals(operator)) {
256 | updateOperations.removeAll(field, value);
257 | }
258 | }
259 | } else {
260 | updateOperations.set(pair.getKey(), pair.getValue());
261 | }
262 | }
263 |
264 | datastore.update(updateQuery, updateOperations);
265 |
266 | future.complete(updateQuery.find().toList());
267 | }, res -> {
268 | if (res.succeeded()) {
269 | resultHandler.handle(Future.succeededFuture((List) res.result()));
270 | } else {
271 | resultHandler.handle(Future.failedFuture(res.cause()));
272 | }
273 | });
274 | }
275 |
276 | public void find(final Class clazz, final JsonObject query, final Handler>> resultHandler) {
277 |
278 | vertx.executeBlocking(future -> {
279 | Query findQuery = datastore.createQuery(clazz);
280 |
281 | for(Iterator> it = query.iterator(); it.hasNext();) {
282 | Map.Entry pair = it.next();
283 | if ("_id".equals(pair.getKey())) {
284 | findQuery.filter("_id", new ObjectId(pair.getValue().toString()));
285 | } else {
286 | findQuery.filter(pair.getKey(), pair.getValue());
287 | }
288 | }
289 |
290 | future.complete(findQuery.find().toList());
291 | }, res -> {
292 | if (res.succeeded()) {
293 | resultHandler.handle(Future.succeededFuture((List) res.result()));
294 | } else {
295 | resultHandler.handle(Future.failedFuture(res.cause()));
296 | }
297 | });
298 | }
299 |
300 | public void delete(final Class clazz, final JsonObject query, final Handler> resultHandler) {
301 | vertx.executeBlocking(future -> {
302 | final Query deleteQuery = datastore.createQuery(clazz);
303 | for(Iterator> it = query.iterator(); it.hasNext();) {
304 | Map.Entry pair = it.next();
305 | if ("_id".equals(pair.getKey())) {
306 | deleteQuery.filter("_id", new ObjectId(pair.getValue().toString()));
307 | } else {
308 | deleteQuery.filter(pair.getKey(), pair.getValue());
309 | }
310 | }
311 | long numDeleted = deleteQuery.count();
312 | datastore.delete(deleteQuery);
313 | future.complete(numDeleted);
314 | }, res -> {
315 | if (res.succeeded()) {
316 | resultHandler.handle(Future.succeededFuture( (Long) res.result()));
317 | } else {
318 | resultHandler.handle(Future.failedFuture(res.cause()));
319 | }
320 | });
321 | }
322 |
323 | private void save(final T entity, final Handler> resultHandler) {
324 | vertx.executeBlocking(future -> {
325 | Key key = this.datastore.save(entity);
326 | future.complete(key.getId().toString());
327 | },res -> {
328 | if (res.succeeded()) {
329 | resultHandler.handle(Future.succeededFuture(res.result().toString()));
330 | } else {
331 | resultHandler.handle(Future.failedFuture(res.cause()));
332 | }
333 | });
334 | }
335 |
336 |
337 | @Override
338 | public void close() {
339 | this.mongoClient.close();
340 | }
341 | }
342 |
--------------------------------------------------------------------------------