From fc76f4fef40a399f4e3ece066f7e5b80c21e6a32 Mon Sep 17 00:00:00 2001 From: Edgar Espina Date: Fri, 22 Apr 2016 14:02:56 -0300 Subject: [PATCH] rxjava-jdbc module fix #351 + add website doc for rxjava + some minor doc updates --- coverage-report/pom.xml | 8 + .../test/java/org/jooby/issues/Issue351.java | 73 +++++++++ jooby-banner/README.md | 47 ++++++ .../jooby/jdbc/HikariDataSourceProvider.java | 5 +- .../src/main/java/org/jooby/jdbc/Jdbc.java | 26 +-- .../test/java/org/jooby/jooq/jOOQTest.java | 6 +- jooby-rxjava-jdbc/README.md | 64 ++++++++ jooby-rxjava-jdbc/pom.xml | 108 +++++++++++++ .../src/main/java/org/jooby/rx/RxJdbc.java | 141 +++++++++++++++++ .../test/java/org/jooby/rx/RxJdbcTest.java | 112 +++++++++++++ jooby-rxjava/README.md | 148 ++++++++++++++++++ .../src/main/java/org/jooby/rx/Rx.java | 55 +++++++ .../org/jooby/internal/HttpHandlerImpl.java | 6 +- md/async.md | 2 +- md/doc/async/async.md | 14 +- md/doc/datastore/datastore.md | 2 + md/doc/more/more.md | 2 +- md/doc/rxjava-jdbc/rxjava-jdbc.md | 64 ++++++++ md/doc/rxjava/rxjava.md | 148 ++++++++++++++++++ pom.xml | 29 ++++ 20 files changed, 1040 insertions(+), 20 deletions(-) create mode 100644 coverage-report/src/test/java/org/jooby/issues/Issue351.java create mode 100644 jooby-banner/README.md create mode 100644 jooby-rxjava-jdbc/README.md create mode 100644 jooby-rxjava-jdbc/pom.xml create mode 100644 jooby-rxjava-jdbc/src/main/java/org/jooby/rx/RxJdbc.java create mode 100644 jooby-rxjava-jdbc/src/test/java/org/jooby/rx/RxJdbcTest.java create mode 100644 jooby-rxjava/README.md create mode 100644 md/doc/rxjava-jdbc/rxjava-jdbc.md create mode 100644 md/doc/rxjava/rxjava.md diff --git a/coverage-report/pom.xml b/coverage-report/pom.xml index 5e8c2db217..98a55d3eae 100644 --- a/coverage-report/pom.xml +++ b/coverage-report/pom.xml @@ -70,6 +70,7 @@ ${project.parent.basedir}/jooby-sitemap/src/main/java ${project.parent.basedir}/jooby-rxjava/src/main/java ${project.parent.basedir}/jooby-banner/src/main/java + ${project.parent.basedir}/jooby-rxjava-jdbc/src/main/java @@ -119,6 +120,7 @@ ${project.parent.basedir}/jooby-sitemap/src/test/java ${project.parent.basedir}/jooby-rxjava/src/test/java ${project.parent.basedir}/jooby-banner/src/test/java + ${project.parent.basedir}/jooby-rxjava-jdbc/src/test/java @@ -351,6 +353,12 @@ ${project.version} + + org.jooby + jooby-rxjava-jdbc + ${project.version} + + com.h2database diff --git a/coverage-report/src/test/java/org/jooby/issues/Issue351.java b/coverage-report/src/test/java/org/jooby/issues/Issue351.java new file mode 100644 index 0000000000..9ad75df157 --- /dev/null +++ b/coverage-report/src/test/java/org/jooby/issues/Issue351.java @@ -0,0 +1,73 @@ +package org.jooby.issues; + +import static org.junit.Assert.assertEquals; + +import org.jooby.rx.Rx; +import org.jooby.rx.RxJdbc; +import org.jooby.test.ServerFeature; +import org.junit.Test; + +import com.github.davidmoten.rx.jdbc.Database; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; + +public class Issue351 extends ServerFeature { + + { + use(ConfigFactory.empty() + .withValue("db", ConfigValueFactory.fromAnyRef("mem"))); + + use(new RxJdbc()); + + onStart(r -> { + Database db = r.require(Database.class); + db.update("create table something (id int primary key, name varchar(100))") + .execute(); + + db.update("insert into something (id, name) values (?, ?)") + .parameters(1, "jooby") + .execute(); + }); + + get("/351/reactive", req -> + req.require(Database.class) + .select("select name from something where id = :id") + .parameter("id", 1) + .getAs(String.class) + ).map(Rx.rx()); + + get("/351/blocking", req -> { + return req.require(Database.class) + .select("select name from something where id = :id") + .parameter("id", 1) + .getAs(String.class) + .toBlocking() + .single(); + }); + + get("/db", req -> { + assertEquals(req.require(Database.class), req.require(Database.class)); + return "OK"; + }); + + } + + @Test + public void rxjdbc() throws Exception { + request().get("/351/reactive") + .expect("jooby"); + } + + @Test + public void rxjdbcBlocking() throws Exception { + request().get("/351/blocking") + .expect("jooby"); + } + + @Test + public void singletondb() throws Exception { + request().get("/db") + .expect("OK"); + } + +} diff --git a/jooby-banner/README.md b/jooby-banner/README.md new file mode 100644 index 0000000000..1bd4d5c946 --- /dev/null +++ b/jooby-banner/README.md @@ -0,0 +1,47 @@ +# banner + +Prints out an ASCII art banner on startup using jfiglet. + +## dependency + +```xml + + org.jooby + jooby-banner + 1.0.0.CR2 + +``` + +## usage + +```java +package com.myapp; +{ + use(new Banner()); + +} +``` + +Prints out the value of ```application.name``` which here is ```myapp```. Or you can specify the text to prints out: + +```java +package com.myapp; +{ + use(new Banner("my awesome app")); + +} +``` + +## font + +You can pick and use the font of your choice via {@link #font(String)} option: + +```java +package com.myapp; +{ + use(new Banner("my awesome app").font("slant")); + +} +``` + +Fonts are distributed within the library inside the ```/flf``` classpath folder. A full list of fonts is available here. diff --git a/jooby-jdbc/src/main/java/org/jooby/jdbc/HikariDataSourceProvider.java b/jooby-jdbc/src/main/java/org/jooby/jdbc/HikariDataSourceProvider.java index 716f611f64..b0bea7f135 100644 --- a/jooby-jdbc/src/main/java/org/jooby/jdbc/HikariDataSourceProvider.java +++ b/jooby-jdbc/src/main/java/org/jooby/jdbc/HikariDataSourceProvider.java @@ -54,7 +54,10 @@ public DataSource get() { } public void stop() { - dataSource.get().close(); + HikariDataSource ds = dataSource.get(); + if (!ds.isClosed()) { + ds.close(); + } } @Override diff --git a/jooby-jdbc/src/main/java/org/jooby/jdbc/Jdbc.java b/jooby-jdbc/src/main/java/org/jooby/jdbc/Jdbc.java index a61b50bfd8..e068f39fef 100644 --- a/jooby-jdbc/src/main/java/org/jooby/jdbc/Jdbc.java +++ b/jooby-jdbc/src/main/java/org/jooby/jdbc/Jdbc.java @@ -44,10 +44,13 @@ import com.zaxxer.hikari.HikariConfig; /** + *

jdbc

+ *

* Production-ready jdbc data source, powered by the * HikariCP library. + *

* - *

Usage

+ *

usage

* *
  * import org.jooby.jdbc.Jdbc;
@@ -64,11 +67,13 @@
  * }
  * 
* - *

db configuration

Database configuration is controlled from your - * application.conf file using the db property and friends: - * db.*. + *

db configuration

+ *

+ * Database configuration is controlled from your application.conf file using the + * db property and friends: db.*. + *

* - *

mem db

+ *

mem db

* *
  *   db = mem
@@ -86,7 +91,7 @@
  *
  * Mem db is useful for dev environment and/or transient data that can be regenerated.
  *
- * 

fs db

+ *

fs db

* *
  *   db = fs
@@ -99,7 +104,10 @@
  * regenerated. Keep in mind this db is saved in a tmp directory and db will be deleted it
  * on restarts.
  *
- * 

db.url

Connect to a database using a jdbc url, some examples here: + *

db.url

+ *

+ * Connect to a database using a jdbc url, some examples here: + *

* *
  *   # mysql
@@ -111,7 +119,7 @@
  * Previous example, show you how to connect to mysql, setting user and password.
  * But of course you need the jdbc driver on your pom.xml:
  *
- * 

hikari configuration

+ *

hikari configuration

*

* If you need to configure or tweak the hikari pool just add hikari.* @@ -149,7 +157,7 @@ *

* *

- * application.conf + * application.conf: *

* *
diff --git a/jooby-jooq/src/test/java/org/jooby/jooq/jOOQTest.java b/jooby-jooq/src/test/java/org/jooby/jooq/jOOQTest.java
index fb22f0ef61..cc70ac97ee 100644
--- a/jooby-jooq/src/test/java/org/jooby/jooq/jOOQTest.java
+++ b/jooby-jooq/src/test/java/org/jooby/jooq/jOOQTest.java
@@ -93,7 +93,7 @@ public class jOOQTest {
     expect(binder.bind(Key.get(DSLContext.class, Names.named("db")))).andReturn(abbC);
   };
 
-  private MockUnit.Block managed = unit -> {
+  private MockUnit.Block onStop = unit -> {
     Env env = unit.get(Env.class);
     expect(env.onStop(isA(CheckedRunnable.class))).andReturn(env);
   };
@@ -104,7 +104,7 @@ public void defaults() throws Exception {
         .expect(jdbc)
         .expect(configuration)
         .expect(ctx)
-        .expect(managed)
+        .expect(onStop)
         .run(unit -> {
           new jOOQ()
               .configure(unit.get(Env.class), config(), unit.get(Binder.class));
@@ -117,7 +117,7 @@ public void doWith() throws Exception {
         .expect(jdbc)
         .expect(configuration)
         .expect(ctx)
-        .expect(managed)
+        .expect(onStop)
         .run(unit -> {
           new jOOQ()
               .doWith(c -> assertEquals(unit.get(Configuration.class), c))
diff --git a/jooby-rxjava-jdbc/README.md b/jooby-rxjava-jdbc/README.md
new file mode 100644
index 0000000000..8460967db5
--- /dev/null
+++ b/jooby-rxjava-jdbc/README.md
@@ -0,0 +1,64 @@
+# rxjdbc
+
+rxjava-jdbc efficient execution, concise code, and functional composition of database calls using JDBC and RxJava Observable.
+
+This module depends on [jdbc module](/doc/jdbc), make sure you read the doc of the [jdbc module](/doc/jdbc) module.
+
+## dependency
+
+```xml
+
+ org.jooby
+ jooby-rxjdbc
+ 1.0.0.CR2
+
+```
+
+## exports
+
+* A ```Database``` object 
+* A [Hikari](https://github.com/brettwooldridge/HikariCP) ```DataSource``` object 
+
+## usage
+
+```java
+import org.jooby.rx.RxJdbc;
+import org.jooby.rx.Rx;
+{
+  use(new RxJdbc());
+
+  get("/reactive", req ->
+    req.require(Database.class)
+      .select("select name from something where id = :id")
+      .parameter("id", 1)
+      .getAs(String.class)
+  ).map(Rx.rx());
+
+}
+```
+
+The [Rx.rx()](/apidocs/org/jooby/rx/Rx.html#rx--) mapper converts ```Observable``` to [deferred](/apidocs/org/jooby/Deferred.html) instances. More at [rx module](/doc/rxjava).
+
+## multiple db connections
+
+```java
+import org.jooby.rx.RxJdbc;
+import org.jooby.rx.Rx;
+{
+  use(new RxJdbc("db.main"));
+
+  use(new RxJdbc("db.audit"));
+
+  get("/", req ->
+
+    Databse db = req.require("db.main", Database.class);
+    Databse audit = req.require("db.audit", Database.class);
+    // ...
+  ).map(Rx.rx());
+
+}
+```
+
+For more details on how to configure the Hikari datasource, please check the [jdbc module](/doc/jdbc).
+
+Happy coding!!!
diff --git a/jooby-rxjava-jdbc/pom.xml b/jooby-rxjava-jdbc/pom.xml
new file mode 100644
index 0000000000..40a559a8bb
--- /dev/null
+++ b/jooby-rxjava-jdbc/pom.xml
@@ -0,0 +1,108 @@
+
+
+
+  
+    org.jooby
+    jooby-project
+    1.0.0.CR3
+  
+
+  4.0.0
+  jooby-rxjava-jdbc
+
+  rx-jdbc module
+
+  
+    
+      
+      
+        org.apache.maven.plugins
+        maven-surefire-plugin
+        
+          
+            **/*Test.java
+            **/*Feature.java
+            **/Issue*.java
+          
+        
+      
+
+    
+  
+
+  
+    
+    
+      org.jooby
+      jooby-jdbc
+    
+
+    
+      org.jooby
+      jooby-rxjava
+    
+
+    
+      io.reactivex
+      rxjava-string
+    
+
+    
+      io.reactivex
+      rxjava-math
+    
+
+    
+      org.slf4j
+      slf4j-api
+    
+
+    
+      com.github.davidmoten
+      rxjava-jdbc
+    
+
+    
+    
+      org.jooby
+      jooby
+      ${project.version}
+      test
+      tests
+    
+
+    
+      junit
+      junit
+      test
+    
+
+    
+      org.easymock
+      easymock
+      test
+    
+
+    
+      org.powermock
+      powermock-api-easymock
+      test
+    
+
+    
+      org.powermock
+      powermock-module-junit4
+      test
+    
+
+    
+      org.jacoco
+      org.jacoco.agent
+      runtime
+      test
+    
+
+  
+
+
diff --git a/jooby-rxjava-jdbc/src/main/java/org/jooby/rx/RxJdbc.java b/jooby-rxjava-jdbc/src/main/java/org/jooby/rx/RxJdbc.java
new file mode 100644
index 0000000000..916b81cba6
--- /dev/null
+++ b/jooby-rxjava-jdbc/src/main/java/org/jooby/rx/RxJdbc.java
@@ -0,0 +1,141 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jooby.rx;
+
+import javax.inject.Provider;
+import javax.sql.DataSource;
+
+import org.jooby.Deferred;
+import org.jooby.Env;
+import org.jooby.jdbc.Jdbc;
+
+import com.github.davidmoten.rx.jdbc.Database;
+import com.google.inject.Binder;
+import com.typesafe.config.Config;
+
+import rx.Observable;
+
+/**
+ * 

rxjdbc

+ *

+ * rxjava-jdbc efficient execution, concise + * code, and functional composition of database calls using JDBC and RxJava Observable. + *

+ * + *

+ * This module depends on {@link Jdbc} module, make sure you read the doc of the {@link Jdbc} + * module. + *

+ * + *

exports

+ *
    + *
  • A {@link Database} object
  • + *
  • A Hikari {@link DataSource} object
  • + *
+ * + *

usage

+ * + *
{@code
+ *
+ * import org.jooby.rx.RxJdbc;
+ * import org.jooby.rx.Rx;
+ *
+ * {
+ *   use(new RxJdbc());
+ *
+ *   get("/reactive", req ->
+ *     req.require(Database.class)
+ *       .select("select name from something where id = :id")
+ *       .parameter("id", 1)
+ *       .getAs(String.class)
+ *   ).map(Rx.rx());
+ * }
+ * }
+ * + *

+ * The {@link Rx#rx()} mapper converts {@link Observable rx observables} to {@link Deferred} + * instances. More at {@link Rx}. + *

+ * + *

multiple db connections

+ * + *
{@code
+ *
+ * import org.jooby.rx.RxJdbc;
+ * import org.jooby.rx.Rx;
+ *
+ * {
+ *   use(new RxJdbc("db.main"));
+ *   use(new RxJdbc("db.audit"));
+ *
+ *   get("/", req ->
+ *     Databse db = req.require("db.main", Database.class);
+ *     Databse audit = req.require("db.audit", Database.class);
+ *     // ...
+ *   ).map(Rx.rx());
+ * }
+ * }
+ * + *

+ * For more details on how to configure the Hikari datasource, please check the {@link Jdbc jdbc + * module} + *

+ * + *

+ * Happy coding!!! + *

+ * + * @author edgar + * @since 1.0.0.CR3 + */ +public class RxJdbc extends Jdbc { + + /** + * Creates a new {@link RxJdbc} module. + * + * @param name Database name. + */ + public RxJdbc(final String name) { + super(name); + } + + /** + * Creates a new {@link RxJdbc} module. + */ + public RxJdbc() { + this(DEFAULT_DB); + } + + @Override + public void configure(final Env env, final Config config, final Binder binder) { + super.configure(env, config, binder); + + Provider p = () -> { + DataSource ds = dataSource().get(); + return Database.fromDataSource(ds); + }; + + keys(Database.class, k -> { + binder.bind(k).toProvider(p).asEagerSingleton(); + + // close on shutdown + env.onStop(r -> r.require(k).close()); + }); + } +} diff --git a/jooby-rxjava-jdbc/src/test/java/org/jooby/rx/RxJdbcTest.java b/jooby-rxjava-jdbc/src/test/java/org/jooby/rx/RxJdbcTest.java new file mode 100644 index 0000000000..c20698d4cc --- /dev/null +++ b/jooby-rxjava-jdbc/src/test/java/org/jooby/rx/RxJdbcTest.java @@ -0,0 +1,112 @@ +package org.jooby.rx; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.isA; + +import javax.inject.Provider; +import javax.sql.DataSource; + +import org.jooby.Env; +import org.jooby.Registry; +import org.jooby.test.MockUnit; +import org.jooby.test.MockUnit.Block; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.github.davidmoten.rx.jdbc.Database; +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.binder.ScopedBindingBuilder; +import com.google.inject.name.Names; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigValueFactory; + +import javaslang.control.Try.CheckedConsumer; +import javaslang.control.Try.CheckedRunnable; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({RxJdbc.class, Database.class }) +public class RxJdbcTest { + + @SuppressWarnings("unchecked") + private Block jdbc = unit -> { + Binder binder = unit.get(Binder.class); + + ScopedBindingBuilder scope = unit.mock(ScopedBindingBuilder.class); + scope.asEagerSingleton(); + scope.asEagerSingleton(); + + LinkedBindingBuilder binding = unit.mock(LinkedBindingBuilder.class); + expect(binding.toProvider(isA(Provider.class))).andReturn(scope).times(2); + expect(binder.bind(Key.get(DataSource.class))).andReturn(binding); + expect(binder.bind(Key.get(DataSource.class, Names.named("db")))).andReturn(binding); + }; + + @SuppressWarnings("unchecked") + private MockUnit.Block onStop = unit -> { + Env env = unit.get(Env.class); + expect(env.onStop(isA(CheckedRunnable.class))).andReturn(env); + + expect(env.onStop(unit.capture(CheckedConsumer.class))).andReturn(env); + expect(env.onStop(unit.capture(CheckedConsumer.class))).andReturn(env); + }; + + @SuppressWarnings("unchecked") + private Block bind = unit -> { + LinkedBindingBuilder lbb = unit.mock(LinkedBindingBuilder.class); + lbb.asEagerSingleton(); + lbb.asEagerSingleton(); + expect(lbb.toProvider(unit.capture(Provider.class))).andReturn(lbb).times(2); + + Binder binder = unit.get(Binder.class); + expect(binder.bind(Key.get(Database.class))).andReturn(lbb); + expect(binder.bind(Key.get(Database.class, Names.named("db")))).andReturn(lbb); + }; + + @Test + public void configure() throws Exception { + new MockUnit(Env.class, Config.class, Binder.class) + .expect(jdbc) + .expect(bind) + .expect(onStop) + .run(unit -> { + new RxJdbc() + .configure(unit.get(Env.class), config(), unit.get(Binder.class)); + }); + } + + @SuppressWarnings("unchecked") + @Test + public void onStop() throws Exception { + new MockUnit(Env.class, Config.class, Binder.class, Registry.class) + .expect(jdbc) + .expect(bind) + .expect(onStop) + .expect(unit -> { + Database db = unit.mock(Database.class); + expect(db.close()).andReturn(db); + + Registry registry = unit.get(Registry.class); + expect(registry.require(Key.get(Database.class))).andReturn(db); + }) + .run(unit -> { + new RxJdbc() + .configure(unit.get(Env.class), config(), unit.get(Binder.class)); + }, unit -> { + unit.captured(CheckedConsumer.class).iterator().next().accept(unit.get(Registry.class)); + }); + } + + private Config config() { + return new RxJdbc().config() + .withValue("db", ConfigValueFactory.fromAnyRef("mem")) + .withValue("application.ns", ConfigValueFactory.fromAnyRef("my.model")) + .withValue("application.tmpdir", ConfigValueFactory.fromAnyRef("target")) + .withValue("application.name", ConfigValueFactory.fromAnyRef("model")) + .withValue("application.charset", ConfigValueFactory.fromAnyRef("UTF-8")) + .resolve(); + } +} diff --git a/jooby-rxjava/README.md b/jooby-rxjava/README.md new file mode 100644 index 0000000000..2b36ea842a --- /dev/null +++ b/jooby-rxjava/README.md @@ -0,0 +1,148 @@ +# rxjava + +Make your web application reactive with RxJava. + +RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences. + +## dependency + +```xml + + org.jooby + jooby-rxjava + 1.0.0.CR2 + +``` + +## exports + +* map route operator: [Rx.rx()](/apidocs/org/jooby/rx/Rx.html#rx--) that converts ```Observable``` (and family) into [Deferred API](/apidocs/org/jooby/Deferred.html). +* manage the lifecycle of ```Schedulers``` and make sure they go down on application shutdown time. +* set a default server thread pool with the number of available processors. + +## usage + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + get("/", req -> Observable.from("reactive programming in jooby!")) + .map(Rx.rx()); +} +``` + +## how it works? + +Previous example is translated to: + +```java +{ + use(new Rx()); + + get("/", req -> { + return new Deferred(deferred -> { + Observable.from("reactive programming in jooby!") + .subscribe(deferred::resolve, deferred::reject); + }); + }); + +} +``` + +Translation is done with the [Rx.rx()](/apidocs/org/jooby/rx/Rx.html#rx--) route operator. If you are a RxJava programmer then you don't need to worry for learning a new API and semantic. The [Rx.rx()](/apidocs/org/jooby/rx/Rx.html#rx--) route operator deal and take cares of the [Deferred API](/apidocs/org/jooby/Deferred.html) API. + +## rx() + +We just learn that we are not force to learn a new API, just write RxJava code. That's cool! + +But.. what if you have 10 routes? 50 routes? + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + get("/1", req -> Observable...) + .map(Rx.rx()); + + get("/2", req -> Observable...) + .map(Rx.rx()); + .... + + get("/N", req -> Observable...) + .map(Rx.rx()); +} +``` + +This is better than written N routes using the [Deferred API](/apidocs/org/jooby/Deferred.html) route by route... but still there is one more option to help you (and your fingers) to right less code: + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + with(() -> { + + get("/1", req -> Observable...); + get("/2", req -> Observable...); + .... + get("/N", req -> Observable...); + + }).map(Rx.rx()); + +} +``` + +**Beautiful, hugh?** + +The [with](/apidocs/org/jooby/Routes.html#with-java.lang.Runnable-) operator let you group any number of routes and apply common attributes and/or operator to all them!!! + +## rx()+scheduler + +You can provide a ```Scheduler``` to the [Rx.rx()](/apidocs/org/jooby/rx/Rx.html#rx--) operator: + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + with(() -> { + + get("/1", req -> Observable...); + get("/2", req -> Observable...); + .... + get("/N", req -> Observable...); + + }).map(Rx.rx(Schedulers::io)); + +} +``` + +All the routes here will ```Observable#subscribeOn(Scheduler)``` the provided ```Scheduler```. + +## schedulers + +This module provides the default ```Scheduler``` from RxJava. But also let you define your own ```Scheduler``` using the [executor module](/doc/executor). + +``` +rx.schedulers.io = forkjoin +rx.schedulers.computation = fixed +rx.schedulers.newThread = "fixed = 10" +``` + +The previous example defines a: + +* forkjoin pool for ```Schedulers#io()``` +* fixed thread pool equals to the number of available processors for ```Schedulers#computation()``` +* fixed thread pool with a max of 10 for ```Schedulers#newThread()``` + +Of course, you can define/override all, some or none of them. In any case the ```Scheduler``` will be shutdown at application shutdown time. diff --git a/jooby-rxjava/src/main/java/org/jooby/rx/Rx.java b/jooby-rxjava/src/main/java/org/jooby/rx/Rx.java index 32ef64d496..91297fd584 100644 --- a/jooby-rxjava/src/main/java/org/jooby/rx/Rx.java +++ b/jooby-rxjava/src/main/java/org/jooby/rx/Rx.java @@ -285,10 +285,65 @@ public Rx() { daemon(true); } + /** + * Map a rx object like {@link Observable}, {@link Single} or {@link Completable} into a + * {@link Deferred} object. + * + *
{@code
+   * ...
+   * import org.jooby.rx.Rx;
+   * ...
+   *
+   * {
+   *   use(new Rx());
+   *
+   *   with(() -> {
+   *     get("/1", req -> Observable...);
+   *
+   *     get("/2", req -> Observable...);
+   *
+   *     ....
+   *
+   *     get("/N", req -> Observable...);
+   *
+   *   }).map(Rx.rx());
+   * }
+   * }
+ * + * @return A new mapper. + */ public static Route.Mapper rx() { return rx(Optional.empty()); } + /** + * Map a rx object like {@link Observable}, {@link Single} or {@link Completable} into a + * {@link Deferred} object. + * + *
{@code
+   * ...
+   * import org.jooby.rx.Rx;
+   * ...
+   *
+   * {
+   *   use(new Rx());
+   *
+   *   with(() -> {
+   *     get("/1", req -> Observable...);
+   *
+   *     get("/2", req -> Observable...);
+   *
+   *     ....
+   *
+   *     get("/N", req -> Observable...);
+   *
+   *   }).map(Rx.rx());
+   * }
+   * }
+ * + * @param subscribeOn An scheduler to subscribeOn. + * @return A new mapper. + */ public static Route.Mapper rx(final Supplier subscribeOn) { return rx(Optional.of(subscribeOn)); } diff --git a/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java b/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java index a3a0afad41..3bfb227923 100644 --- a/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java +++ b/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java @@ -61,6 +61,7 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; +import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -342,7 +343,7 @@ private void onDeferred(final Map scope, final NativeRequest req private void handleErr(final RequestImpl req, final ResponseImpl rsp, final Throwable ex) { try { - log.debug("execution of: " + req.method() + req.path() + " resulted in exception", ex); + log.debug("execution of: {}{} resulted in exception", req.method(), req.path(), ex); rsp.reset(); @@ -361,7 +362,8 @@ private void handleErr(final RequestImpl req, final ResponseImpl rsp, final Thro next.handle(req, rsp, err); } } catch (Throwable errex) { - log.error("execution of err handler resulted in exception", errex); + log.error("execution of: {}{} resulted in exception\n{}Caused by:", + req.method(), req.path(), Throwables.getStackTraceAsString(errex), ex); } } diff --git a/md/async.md b/md/async.md index 9c0b9f568a..766b6852a1 100644 --- a/md/async.md +++ b/md/async.md @@ -4,7 +4,7 @@ In {{jooby}} all the IO operations are performed in async & non blocking fashion All IO operations run in an IO thread while application logic **always run in a worker thread**. Worker threads can block at any time. -Having said that let see what tools are available for async/reactive programming. +Having said that let see what tools are available for reactive/async programming. ## threads diff --git a/md/doc/async/async.md b/md/doc/async/async.md index 8ea72d749f..f2fadf9157 100644 --- a/md/doc/async/async.md +++ b/md/doc/async/async.md @@ -1,8 +1,6 @@ {{async.md}} -# modules - -Let's see what modules are available: +# reactive & async modules ## akka @@ -11,3 +9,13 @@ Let's see what modules are available: ## executor * [executor](/doc/executor): async processing via Java ```Executors``` + +## reactive + +* [rx](/doc/rxjava): build reactive web applications via rxjava + +* [rxjdbc](/doc/rxjava-jdbc): efficient execution, concise code, and functional composition of database calls using JDBC and RxJava Observable + +## job scheduling + +* [quartz](/doc/quartz): advanced job scheduling via {{quartz}}. diff --git a/md/doc/datastore/datastore.md b/md/doc/datastore/datastore.md index 3f556363cd..aae4cf4f1d 100644 --- a/md/doc/datastore/datastore.md +++ b/md/doc/datastore/datastore.md @@ -26,6 +26,8 @@ The [jdbc](/doc/jdbc) module provides a high performance connection pool for jdb * [querydsl-jpa](/doc/querydsl-jpa): unified Queries for JPA. [Querydsl](http://www.querydsl.com/) is compact, safe and easy to learn. +* [rxjdbc](/doc/rxjava-jdbc): efficient execution, concise code, and functional composition of database calls using JDBC and RxJava Observable + ## migrations * [flyway](/doc/flyway): provides database migrations via {{flyway}}. diff --git a/md/doc/more/more.md b/md/doc/more/more.md index 13ecd1f9f0..9222220cd1 100644 --- a/md/doc/more/more.md +++ b/md/doc/more/more.md @@ -18,7 +18,7 @@ * [sitemap](/doc/sitemap): automatically generates ```sitemap.xml``` file. -# scheduling +# job scheduling * [quartz](/doc/quartz): advanced job scheduling via {{quartz}}. diff --git a/md/doc/rxjava-jdbc/rxjava-jdbc.md b/md/doc/rxjava-jdbc/rxjava-jdbc.md new file mode 100644 index 0000000000..bd81dc8f77 --- /dev/null +++ b/md/doc/rxjava-jdbc/rxjava-jdbc.md @@ -0,0 +1,64 @@ +# rxjdbc + +rxjava-jdbc efficient execution, concise code, and functional composition of database calls using JDBC and RxJava Observable. + +This module depends on [jdbc module](/doc/jdbc), make sure you read the doc of the [jdbc module](/doc/jdbc) module. + +## dependency + +```xml + + org.jooby + jooby-rxjdbc + {{version}} + +``` + +## exports + +* A ```Database``` object +* A {{hikari}} ```DataSource``` object + +## usage + +```java +import org.jooby.rx.RxJdbc; +import org.jooby.rx.Rx; +{ + use(new RxJdbc()); + + get("/reactive", req -> + req.require(Database.class) + .select("select name from something where id = :id") + .parameter("id", 1) + .getAs(String.class) + ).map(Rx.rx()); + +} +``` + +The [Rx.rx()]({{defdocs}}/rx/Rx.html#rx--) mapper converts ```Observable``` to [deferred]({{defdocs}}/Deferred.html) instances. More at [rx module](/doc/rxjava). + +## multiple db connections + +```java +import org.jooby.rx.RxJdbc; +import org.jooby.rx.Rx; +{ + use(new RxJdbc("db.main")); + + use(new RxJdbc("db.audit")); + + get("/", req -> + + Databse db = req.require("db.main", Database.class); + Databse audit = req.require("db.audit", Database.class); + // ... + ).map(Rx.rx()); + +} +``` + +For more details on how to configure the Hikari datasource, please check the [jdbc module](/doc/jdbc). + +Happy coding!!! diff --git a/md/doc/rxjava/rxjava.md b/md/doc/rxjava/rxjava.md new file mode 100644 index 0000000000..20e94cb540 --- /dev/null +++ b/md/doc/rxjava/rxjava.md @@ -0,0 +1,148 @@ +# rxjava + +Make your web application reactive with RxJava. + +RxJava is a Java VM implementation of Reactive Extensions: a library for composing asynchronous and event-based programs by using observable sequences. + +## dependency + +```xml + + org.jooby + jooby-rxjava + {{version}} + +``` + +## exports + +* map route operator: [Rx.rx()]({{defdocs}}/rx/Rx.html#rx--) that converts ```Observable``` (and family) into [Deferred API]({{defdocs}}/Deferred.html). +* manage the lifecycle of ```Schedulers``` and make sure they go down on application shutdown time. +* set a default server thread pool with the number of available processors. + +## usage + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + get("/", req -> Observable.from("reactive programming in jooby!")) + .map(Rx.rx()); +} +``` + +## how it works? + +Previous example is translated to: + +```java +{ + use(new Rx()); + + get("/", req -> { + return new Deferred(deferred -> { + Observable.from("reactive programming in jooby!") + .subscribe(deferred::resolve, deferred::reject); + }); + }); + +} +``` + +Translation is done with the [Rx.rx()]({{defdocs}}/rx/Rx.html#rx--) route operator. If you are a RxJava programmer then you don't need to worry for learning a new API and semantic. The [Rx.rx()]({{defdocs}}/rx/Rx.html#rx--) route operator deal and take cares of the [Deferred API]({{defdocs}}/Deferred.html) API. + +## rx() + +We just learn that we are not force to learn a new API, just write RxJava code. That's cool! + +But.. what if you have 10 routes? 50 routes? + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + get("/1", req -> Observable...) + .map(Rx.rx()); + + get("/2", req -> Observable...) + .map(Rx.rx()); + .... + + get("/N", req -> Observable...) + .map(Rx.rx()); +} +``` + +This is better than written N routes using the [Deferred API]({{defdocs}}/Deferred.html) route by route... but still there is one more option to help you (and your fingers) to right less code: + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + with(() -> { + + get("/1", req -> Observable...); + get("/2", req -> Observable...); + .... + get("/N", req -> Observable...); + + }).map(Rx.rx()); + +} +``` + +**Beautiful, hugh?** + +The [with]({{defdocs}}/Routes.html#with-java.lang.Runnable-) operator let you group any number of routes and apply common attributes and/or operator to all them!!! + +## rx()+scheduler + +You can provide a ```Scheduler``` to the [Rx.rx()]({{defdocs}}/rx/Rx.html#rx--) operator: + +```java +... +import org.jooby.rx.Rx; +... +{ + use(new Rx()); + + with(() -> { + + get("/1", req -> Observable...); + get("/2", req -> Observable...); + .... + get("/N", req -> Observable...); + + }).map(Rx.rx(Schedulers::io)); + +} +``` + +All the routes here will ```Observable#subscribeOn(Scheduler)``` the provided ```Scheduler```. + +## schedulers + +This module provides the default ```Scheduler``` from RxJava. But also let you define your own ```Scheduler``` using the [executor module](/doc/executor). + +``` +rx.schedulers.io = forkjoin +rx.schedulers.computation = fixed +rx.schedulers.newThread = "fixed = 10" +``` + +The previous example defines a: + +* forkjoin pool for ```Schedulers#io()``` +* fixed thread pool equals to the number of available processors for ```Schedulers#computation()``` +* fixed thread pool with a max of 10 for ```Schedulers#newThread()``` + +Of course, you can define/override all, some or none of them. In any case the ```Scheduler``` will be shutdown at application shutdown time. diff --git a/pom.xml b/pom.xml index 073c0d2e1f..c4d331fc95 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ jooby-querydsl jooby-sitemap jooby-rxjava + jooby-rxjava-jdbc jooby-banner coverage-report @@ -467,6 +468,12 @@ ${jooby.version} + + org.jooby + jooby-rxjava-jdbc + ${jooby.version} + + de.undercouch @@ -1068,6 +1075,25 @@ ${metrics.version} + + + io.reactivex + rxjava-string + ${rxjava-string.version} + + + + io.reactivex + rxjava-math + ${rxjava-math.version} + + + + com.github.davidmoten + rxjava-jdbc + ${rxjava-jdbc.version} + + org.codehaus.mojo @@ -2248,6 +2274,9 @@ org.eclipse.jdt.apt.processorOptions/defaultOverwrite=true 1.1.4 1.8.3 1.1.3 + 1.1.0 + 1.0.0 + 0.7 **