From a4f5d7eb8470dfde04351de6d05c381747baa0ee Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 17 Feb 2017 12:46:55 +0100 Subject: [PATCH 01/11] Change version to 1.1 snapshot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 14ca1b5..7cf264a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.0.2.BUILD-SNAPSHOT" +version "1.1.0.BUILD-SNAPSHOT" group "org.grails.plugins" apply plugin:"eclipse" From f58ef04f6ae3e4b2fb0823923683228bfe6fb131 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 17 Feb 2017 12:55:08 +0100 Subject: [PATCH 02/11] Document server sent event customization --- src/docs/asciidoc/serverSentEvents.adoc | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/docs/asciidoc/serverSentEvents.adoc b/src/docs/asciidoc/serverSentEvents.adoc index e45a926..d0740f3 100644 --- a/src/docs/asciidoc/serverSentEvents.adoc +++ b/src/docs/asciidoc/serverSentEvents.adoc @@ -74,4 +74,42 @@ function tickTock() { document.getElementById('message').innerHTML = event.data; }, false); } +---- + +In addition for more complex event types you can use the `rx.event` method: + +[source,groovy] +---- + rx.stream { Subscriber subscriber -> + for(i in (0..5)) { + if(i % 2 == 0) { + subscriber.onNext( + rx.event("Tick", event:"Event $i", id:"$id", comment:"Tick Event") <1> + ) + } + else { + ... + } + } + ... + } +---- + +<1> The `event` method can be used to customize the Server Sent event properties and rendering multi-line data. + +The `event` method also accepts `Writable` instances, which can be useful in combination with builders: + +[source,groovy] +---- +Writable writable = new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + new StreamingJsonBuilder(writer).call { + tick "Tock" + } + return writer + } +} +rx.event(writable, event:"Event $i", id:"$id", comment:"Tick Event") + ---- \ No newline at end of file From 8daa7f9a9bce3be9dfbe80d1d287df5281c90181 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 17 Feb 2017 13:56:44 +0100 Subject: [PATCH 03/11] Update example to RxJava 1.x --- .../gradle.properties | 2 +- .../rxjava/demo/TickTockController.groovy | 38 +++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/examples/server-sent-event-example/gradle.properties b/examples/server-sent-event-example/gradle.properties index 62d5d8e..2733d6b 100644 --- a/examples/server-sent-event-example/gradle.properties +++ b/examples/server-sent-event-example/gradle.properties @@ -1,2 +1,2 @@ -grailsVersion=3.2.5 +grailsVersion=3.2.6 gradleWrapperVersion=2.13 diff --git a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy index d34bde9..6d277f1 100644 --- a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy +++ b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy @@ -2,14 +2,12 @@ package rxjava.demo import grails.converters.JSON import grails.rx.web.* -import io.reactivex.Emitter -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject import reactor.spring.context.annotation.Consumer import reactor.spring.context.annotation.Selector - +import rx.Observable +import rx.Subscriber +import rx.subjects.* +import rx.schedulers.Schedulers import java.util.concurrent.TimeUnit /** @@ -19,7 +17,7 @@ import java.util.concurrent.TimeUnit class TickTockController implements RxController { def index() { - rx.stream { Emitter emitter -> + rx.stream { Subscriber emitter -> for(i in (0..5)) { if(i % 2 == 0) { emitter.onNext( @@ -34,7 +32,7 @@ class TickTockController implements RxController { } sleep 1000 } - emitter.onComplete() + emitter.onCompleted() } } @@ -42,7 +40,7 @@ class TickTockController implements RxController { def lastId = request.getHeader('Last-Event-ID') as Integer def startId = lastId ? lastId + 1 : 0 log.info("Last Event ID: $lastId") - rx.stream { Emitter emitter -> + rx.stream { Subscriber emitter -> log.info("SSE Thread ${Thread.currentThread().name}") for(i in (startId..(startId+9))) { if(i % 2 == 0) { @@ -58,7 +56,7 @@ class TickTockController implements RxController { } sleep 1000 } - emitter.onComplete() + emitter.onCompleted() } } @@ -72,7 +70,15 @@ class TickTockController implements RxController { .doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") } .map { def id = it + startId - rx.event([type: 'observable', num: id] as JSON, id: id, comment: 'hello') + def json = [type: 'observable', num: id] as JSON + + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, id: id, comment: 'hello') } .take(10), ) @@ -94,7 +100,15 @@ class TickTockController implements RxController { .doOnError { log.info("Quartz thread error") } .map { log.info("Quartz Thread ${Thread.currentThread().name}") - rx.event([type: 'quartz', num: it as int] as JSON, comment: 'hello') + + def json = [type: 'quartz', num: it as int] as JSON + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, comment: 'hello') } ) } From dcf061e2dbbe88109d7a7d680cf30841622e2bf4 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 17 Feb 2017 14:03:22 +0100 Subject: [PATCH 04/11] cleanup --- .../rxjava/demo/TickTockController.groovy | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy index 6d277f1..3104d14 100644 --- a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy +++ b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy @@ -18,15 +18,14 @@ class TickTockController implements RxController { def index() { rx.stream { Subscriber emitter -> - for(i in (0..5)) { - if(i % 2 == 0) { + for (i in (0..5)) { + if (i % 2 == 0) { emitter.onNext( - rx.render("Tick") + rx.render("Tick") ) - } - else { + } else { emitter.onNext( - rx.render("Tock") + rx.render("Tock") ) } @@ -42,13 +41,12 @@ class TickTockController implements RxController { log.info("Last Event ID: $lastId") rx.stream { Subscriber emitter -> log.info("SSE Thread ${Thread.currentThread().name}") - for(i in (startId..(startId+9))) { - if(i % 2 == 0) { + for (i in (startId..(startId + 9))) { + if (i % 2 == 0) { emitter.onNext( rx.event("Tick\n$i", id: i, event: 'tick', comment: 'tick') ) - } - else { + } else { emitter.onNext( rx.event("Tock\n$i", id: i, event: 'tock', comment: 'tock') ) @@ -64,23 +62,23 @@ class TickTockController implements RxController { def lastId = request.getHeader('Last-Event-ID') as Integer def startId = lastId ? lastId + 1 : 0 rx.stream( - Observable - .interval(1, TimeUnit.SECONDS) - .doOnSubscribe { log.info("Observable Subscribe Thread ${Thread.currentThread().name}") } - .doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") } - .map { - def id = it + startId - def json = [type: 'observable', num: id] as JSON + Observable + .interval(1, TimeUnit.SECONDS) + .doOnSubscribe { log.info("Observable Subscribe Thread ${Thread.currentThread().name}") } + .doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") } + .map { + def id = it + startId + def json = [type: 'observable', num: id] as JSON - rx.event(new Writable() { - @Override - Writer writeTo(Writer writer) throws IOException { - json.render(writer) - return writer - } - }, id: id, comment: 'hello') - } - .take(10), + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, id: id, comment: 'hello') + } + .take(10) ) } @@ -95,21 +93,21 @@ class TickTockController implements RxController { def quartz() { rx.stream( - publishedObservable - .doOnSubscribe { log.info("Quartz Subscribe Thread ${Thread.currentThread().name}") } - .doOnError { log.info("Quartz thread error") } - .map { - log.info("Quartz Thread ${Thread.currentThread().name}") + publishedObservable + .doOnSubscribe { log.info("Quartz Subscribe Thread ${Thread.currentThread().name}") } + .doOnError { log.info("Quartz thread error") } + .map { + log.info("Quartz Thread ${Thread.currentThread().name}") - def json = [type: 'quartz', num: it as int] as JSON - rx.event(new Writable() { - @Override - Writer writeTo(Writer writer) throws IOException { - json.render(writer) - return writer - } - }, comment: 'hello') - } + def json = [type: 'quartz', num: it as int] as JSON + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, comment: 'hello') + } ) } } From c3616d123534552c9fe624ca32e66d47c36e0f83 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Wed, 1 Mar 2017 16:29:43 +0100 Subject: [PATCH 05/11] Upgrade to RxJava 1.2.7 / Refine observable handling --- build.gradle | 2 +- src/docs/asciidoc/gettingStarted.adoc | 2 +- src/docs/asciidoc/serverSentEvents.adoc | 14 ++-- src/main/groovy/grails/rx/web/Rx.groovy | 82 ++++++++++--------- .../plugins/rx/web/NewObservableResult.groovy | 7 +- .../plugins/rx/web/RxResultTransformer.groovy | 15 +++- .../org/grails/plugins/rx/EventSpec.groovy | 14 ++-- .../plugins/rx/NewObservableResultSpec.groovy | 19 +++-- .../org/grails/plugins/rx/RenderSpec.groovy | 34 ++++++++ 9 files changed, 118 insertions(+), 71 deletions(-) diff --git a/build.gradle b/build.gradle index 7cf264a..571fbd8 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ dependencies { compile "org.springframework.boot:spring-boot-starter-tomcat" compile "org.grails:grails-dependencies" compile "org.grails:grails-web-boot" - compile 'io.reactivex:rxjava:1.2.6' + compile 'io.reactivex:rxjava:1.2.7' console "org.grails:grails-console" diff --git a/src/docs/asciidoc/gettingStarted.adoc b/src/docs/asciidoc/gettingStarted.adoc index da03503..1d5747e 100644 --- a/src/docs/asciidoc/gettingStarted.adoc +++ b/src/docs/asciidoc/gettingStarted.adoc @@ -16,7 +16,7 @@ You then may want to include an implementation of RxGORM, for the examples in th ---- dependencies { ... - compile 'org.grails.plugins:rx-mongodb:6.0.0.M2' + compile 'org.grails.plugins:rx-mongodb:6.0.7' } ---- diff --git a/src/docs/asciidoc/serverSentEvents.adoc b/src/docs/asciidoc/serverSentEvents.adoc index d0740f3..6f33272 100644 --- a/src/docs/asciidoc/serverSentEvents.adoc +++ b/src/docs/asciidoc/serverSentEvents.adoc @@ -8,15 +8,15 @@ For example: ---- def index() { - rx.stream { Subscriber subscriber -> <1> + rx.stream { rx.Observer observer -> <1> for(i in (0..5)) { if(i % 2 == 0) { - subscriber.onNext( + observer.onNext( rx.render("Tick") <2> ) } else { - subscriber.onNext( + observer.onNext( rx.render("Tock") ) @@ -28,7 +28,7 @@ def index() { } ---- -<1> Call the `stream` method passing a closure that accepts an `rx.Subscriber` to start sending events +<1> Call the `stream` method passing a closure that accepts an `rx.Observer` to start sending events <2> Emit a one or many items using `onNext` <3> Call `sleep` to simulate a slow request <4> Call `onCompleted` to complete the request @@ -60,7 +60,7 @@ If you wish to send a particular named event you can use the name argument of th [source,groovy] ---- -stream "ticktock", { Subscriber subscriber -> +stream "ticktock", { rx.Observer observer -> ---- And then attach an event listener for only that event on the client: @@ -80,10 +80,10 @@ In addition for more complex event types you can use the `rx.event` method: [source,groovy] ---- - rx.stream { Subscriber subscriber -> + rx.stream { rx.Observer observer -> for(i in (0..5)) { if(i % 2 == 0) { - subscriber.onNext( + observer.onNext( rx.event("Tick", event:"Event $i", id:"$id", comment:"Tick Event") <1> ) } diff --git a/src/main/groovy/grails/rx/web/Rx.groovy b/src/main/groovy/grails/rx/web/Rx.groovy index 93cb49a..9da7a13 100644 --- a/src/main/groovy/grails/rx/web/Rx.groovy +++ b/src/main/groovy/grails/rx/web/Rx.groovy @@ -1,6 +1,5 @@ package grails.rx.web -import grails.async.Promises import grails.web.databinding.DataBindingUtils import grails.web.mapping.mvc.exceptions.CannotRedirectException import groovy.transform.CompileDynamic @@ -17,10 +16,11 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.context.request.async.WebAsyncManager import org.springframework.web.context.request.async.WebAsyncUtils +import rx.Emitter import rx.Observable import rx.Subscriber +import rx.functions.Action1 -import javax.servlet.ServletInputStream import javax.servlet.http.HttpServletRequest import java.util.concurrent.TimeUnit @@ -223,36 +223,36 @@ class Rx { */ @CompileDynamic static Observable bindData(Object object, Object bindingSource, Map arguments = Collections.emptyMap(), String filter = null) { - Observable.create( { Subscriber subscriber -> - subscriber.onStart() - Promises.task { - if(bindingSource instanceof HttpServletRequest) { - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource) - // horrible hack, find better solution - GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null - if(webRequest != null) { - RequestContextHolder.setRequestAttributes(webRequest) - try { - List includeList = convertToListIfCharSequence(arguments?.include) - List excludeList = convertToListIfCharSequence(arguments?.exclude) - - DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter) - } finally { - RequestContextHolder.setRequestAttributes(null) - } - } - else { - object.properties = bindingSource + Observable.create( { Emitter emitter -> + try { + if(bindingSource instanceof HttpServletRequest) { + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource) + // horrible hack, find better solution + GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null + if(webRequest != null) { + RequestContextHolder.setRequestAttributes(webRequest) + try { + List includeList = convertToListIfCharSequence(arguments?.include) + List excludeList = convertToListIfCharSequence(arguments?.exclude) + + DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter) + } finally { + RequestContextHolder.setRequestAttributes(null) } } else { object.properties = bindingSource } - subscriber.onNext(object) - subscriber.onCompleted() - } - } as Observable.OnSubscribe) + else { + object.properties = bindingSource + } + emitter.onNext(object) + emitter.onCompleted() + } catch (Throwable e) { + emitter.onError(e) + } + } as Action1>, Emitter.BackpressureMode.NONE) } @@ -264,24 +264,28 @@ class Rx { * @return An observable */ static Observable fromBody(HttpServletRequest request) { - Observable.create( { Subscriber subscriber -> - subscriber.onStart() - Promises.task { - InputStream inputStream = null + Observable.create( { Emitter subscriber -> + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request) + if(!asyncManager.isConcurrentHandlingStarted()) { + throw new IllegalStateException("Servlet async processing must have been started to use fromBody(..)") + } + InputStream inputStream = null + try { try { - try { - inputStream = request.getInputStream() - subscriber.onNext(inputStream) - } catch (Throwable e) { - subscriber.onError(e) - } - } finally { + inputStream = request.getInputStream() + subscriber.onNext(inputStream) subscriber.onCompleted() + } catch (Throwable e) { + subscriber.onError(e) + } + } finally { + try { inputStream?.close() + } catch (Throwable e) { + // ignore } - } - } as Observable.OnSubscribe) + } as Action1>, Emitter.BackpressureMode.NONE) } /** diff --git a/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy b/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy index 6d4b6a3..97e580e 100644 --- a/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy +++ b/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy @@ -1,8 +1,7 @@ package org.grails.plugins.rx.web import groovy.transform.CompileStatic -import rx.Observable -import rx.Subscriber +import rx.Observer import java.util.concurrent.TimeUnit @@ -21,9 +20,9 @@ class NewObservableResult extends TimeoutResult { super(timeout, unit) this.callable = callable def parameterTypes = this.callable.parameterTypes - boolean isSubscriber = parameterTypes.length == 1 && Subscriber.isAssignableFrom(parameterTypes[0]) + boolean isSubscriber = parameterTypes.length == 1 && Observer.isAssignableFrom(parameterTypes[0]) if(!isSubscriber) { - throw new IllegalArgumentException("Passed closure must accept argument of type rx.Subscriber") + throw new IllegalArgumentException("Passed closure must accept argument of type rx.Observer") } } diff --git a/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy b/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy index 8a4c8ed..8309c62 100644 --- a/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy +++ b/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy @@ -15,8 +15,11 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.context.request.async.AsyncWebRequest import org.springframework.web.context.request.async.WebAsyncManager import org.springframework.web.context.request.async.WebAsyncUtils +import rx.Emitter import rx.Observable import rx.Subscriber +import rx.functions.Action1 +import rx.schedulers.Schedulers import javax.servlet.AsyncContext import javax.servlet.ServletResponse @@ -117,18 +120,22 @@ class RxResultTransformer implements ActionResultTransformer { // in a separate thread register the observable subscriber asyncContext.start { - observable.subscribe(subscriber) + observable.observeOn(Schedulers.immediate()) // run on the async servlet thread + .subscribe(subscriber) } } else { asyncContext.start { NewObservableResult newObservableResult = (NewObservableResult)actionResult - Observable newObservable = Observable.create( { Subscriber newSub -> + + Observable newObservable = Observable.create( { Emitter newSub -> Closure callable = newObservableResult.callable callable.setDelegate(newSub) callable.call(newSub) - } as Observable.OnSubscribe) - newObservable.subscribe(subscriber) + } as Action1, Emitter.BackpressureMode.NONE) + newObservable + .observeOn(Schedulers.immediate()) // run on the async servlet thread + .subscribe(subscriber) } } // return null indicating that the request thread should be returned to the thread pool diff --git a/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy b/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy index 0019634..0bea2ef 100644 --- a/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy @@ -13,6 +13,8 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.grails.web.util.GrailsApplicationAttributes import org.springframework.mock.web.MockHttpServletRequest import org.springframework.web.context.request.RequestContextHolder +import rx.Emitter +import rx.Observer import rx.Subscriber import spock.lang.Specification @@ -126,23 +128,23 @@ class EventController implements Controller, RestResponder{ RxHelper rx = new RxHelper() def stream() { - rx.stream { Subscriber subscriber -> + rx.stream { Observer observer -> for(i in 0..3) { - subscriber.onNext( + observer.onNext( rx.event("Foo $i\nFoo\nBar\nBaz", event: "Event $i", comment: 'potato', id: "$i") ) } - subscriber.onCompleted() + observer.onCompleted() } } def streamJson() { - rx.stream { Subscriber subscriber -> + rx.stream { Emitter emitter -> for(i in 0..3) { def foo = """bar $i bar $i""" def json = [foo: foo, 'baz': 3] as JSON - subscriber.onNext( + emitter.onNext( rx.event(new Writable() { @Override Writer writeTo(Writer writer) throws IOException { @@ -151,7 +153,7 @@ bar $i""" }, id: "$i", retry: 1000) ) } - subscriber.onCompleted() + emitter.onCompleted() } } } \ No newline at end of file diff --git a/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy b/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy index 614aa19..08f9ef0 100644 --- a/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy @@ -14,6 +14,7 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.grails.web.util.GrailsApplicationAttributes import org.springframework.mock.web.MockHttpServletRequest import org.springframework.web.context.request.RequestContextHolder +import rx.Emitter import rx.Subscriber import spock.lang.Specification import static grails.rx.web.Rx.* @@ -119,35 +120,35 @@ data: {"foo":"bar 3"} } class NewObservableController implements Controller, RestResponder{ def index() { - create { Subscriber subscriber -> - subscriber.onNext( + create { Emitter emitter -> + emitter.onNext( render("Foo") ) - subscriber.onCompleted() + emitter.onCompleted() } } def stream() { - stream { Subscriber subscriber -> + stream { Emitter emittter -> for(i in 0..3) { - subscriber.onNext( + emittter.onNext( render("Foo $i") ) } - subscriber.onCompleted() + emittter.onCompleted() } } def streamJson() { - stream { Subscriber subscriber -> + stream { Emitter emitter -> for(i in 0..3) { - subscriber.onNext( + emitter.onNext( render(contentType:"application/json") { foo "bar $i" } ) } - subscriber.onCompleted() + emitter.onCompleted() } } } diff --git a/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy b/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy index 8ee5e7e..9cc6e15 100644 --- a/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy @@ -131,10 +131,44 @@ class RenderSpec extends Specification { RequestContextHolder.setRequestAttributes(null) } + + + void "Test render a from body async"() { + setup: + GrailsWebRequest webRequest = GrailsWebMockUtil.bindMockWebRequest() + MockHttpServletRequest request = webRequest.getCurrentRequest() + request.setAsyncSupported(true) + RenderController controller = new RenderController() + request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller) + request.setContent("Foo".bytes) + when:"A controller uses the render method and a string" + Observable observable = controller.renderFromBody() + + then: + observable != null + + when:"The observable is transformed" + RxResultTransformer transformer = new RxResultTransformer() + def result = transformer.transformActionResult(webRequest, "renderText", observable) + then:"null is returned" + result == null + webRequest.response.contentAsString == "Foo" + + cleanup: + ConvertersConfigurationHolder.clear() + RequestContextHolder.setRequestAttributes(null) + + } } class RenderController implements Controller { + def renderFromBody() { + fromBody(request) + .map { InputStream input -> + render(input.text) + } + } def renderView() { Observable.just("Foo") .map { String result -> From 7c263d005556bf123f674fe871828db84f065656 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Wed, 1 Mar 2017 16:37:49 +0100 Subject: [PATCH 06/11] Release 1.1.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 571fbd8..649a891 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.1.0.BUILD-SNAPSHOT" +version "1.1.0" group "org.grails.plugins" apply plugin:"eclipse" From 014379d73c06fad69c88ef2660495efe7216c1bb Mon Sep 17 00:00:00 2001 From: graemerocher Date: Wed, 1 Mar 2017 16:42:50 +0100 Subject: [PATCH 07/11] Back to snapshot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 649a891..aab95bf 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.1.0" +version "1.1.1.BUILD-SNAPSHOT" group "org.grails.plugins" apply plugin:"eclipse" From 9e92ddfab50c083b8f4fe9ee0d0bdfe0683c5344 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 23 Jun 2017 15:51:57 +0200 Subject: [PATCH 08/11] Upgrade to RxJava 1.3.x --- build.gradle | 2 +- gradle.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aab95bf..0345b3d 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ dependencies { compile "org.springframework.boot:spring-boot-starter-tomcat" compile "org.grails:grails-dependencies" compile "org.grails:grails-web-boot" - compile 'io.reactivex:rxjava:1.2.7' + compile "io.reactivex:rxjava:$rxJavaVersion" console "org.grails:grails-console" diff --git a/gradle.properties b/gradle.properties index cf7ef73..a2abf34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ grailsVersion=3.1.13 gradleWrapperVersion=2.13 +rxJavaVersion=1.3.0 From 141945fcdcbc7b173cf0ff7318fa4da5bb3fc58c Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 23 Jun 2017 16:12:56 +0200 Subject: [PATCH 09/11] Fix tokens --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c671b9..d52cf8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,15 @@ jdk: - openjdk7 before_script: - rm -rf target -script: ./travis-build.sh +script: "./travis-build.sh" env: global: - GIT_NAME="Graeme Rocher" - GIT_EMAIL="graeme.rocher@gmail.com" - - secure: tskmGxcLqxs8LpgeF73bcKNw2hCQvd+i3t63xpLlGddg1LB6L2LyytbM3jF5Ynif39LUSwsACSly33EywkXDAEEtkGUEhSOL/piZQcd/YidCq5Xdf1SXLZJYjLsfohu8mi3PVue33TWLucYzqvvR1jO9xEoKjw0BPfbiJt5mniswl2O/pRJjiDmqfYJZVClZu9fe18/eAvhaBYzNX6CEU3wvrymBiQVpTGVbV1UWaQKFHv2lGKhnpF2UM+F182/GU59gYsm/gn+5+6xUapAfEHGGv6kr+ZnB5E3oHigQKqT6B/zOZf7RHtuHxtiOZx1WoOYedpl++oMae9mdX+s5hXYvxH2DHe18k/RDAEl04Ae0qt9nK98eYMuTDS1HEYxAG5yuFbjlK46Nx7ANhjdeVzJdFjTwpmXNpbraVqlF4/3/JYzqcdumGJippHa+5ejlPXP20csGejIhJYufoRvRgvYz+nloYg8s/6cyKcUZx1Nm3gTdNZVPhYZJN0w9wtWo5OyTTSAwKsejex+95LvcTDF6AvDAGm97CByTWNV0QsQ7fbmMIBHEfVIsGEXkzvzSoq11ran1+ZO8uiELIYZccwQ4GKy+Q0a8DxwRJ2yP5fIY+srsNIY4rZViy1xe6SDdUmqaNyQepY+gzNjVmBsVq0C4GdcXsGxLxY63vOzjoBo= - - secure: 0bTHsSCK5YFDd2bUIVH0W88F/ZWLCpVf4KOuC6Qgj/fv8RjELlnGc998INq2KGd0jaS2Il8OWf8YC171Frkt3senwiYE/MZANJeKRIuloJHnhgUdKBQgerpU/YSpGRUk+kTban7MZgg8f9qbHhkbrtTymLJTCvWz8YribPlyVIaz1n/Dv/4ddBUWHMW8cn8LChiF3WQN4KoL9Jd23XKm0mfOUFGeEqJC5c18Fj7NPsFpacWfpOna9+D9ncBcvHM1ktJ9+FyUA7dCrarRBQaPKcPluzvCJ7BBWlQYN5NUpV54FGn+MphtqBfxvlsZb2LTjK0O6j/4HiazGskZdYul0ige4O6+TCDtce8DUrvcRLCIfrd4dU8pJmSFLKDiervk0UhqWhuD6UB2ttrs+HSOjSvXLz4FlAHvF/choiFYVIP/iNkRaQP1EvyA65RocYgGfEOxZrFlqFipcSBLiFOL3zxh5lAEjR8JzmLM6EiU3dORwQVgB+Zpqj4FaeSjojRYI90/0AMQ/y5/FvxXhhg7tNfpn1q8Cs/A31kdvz3P/iuNTZXCxD68dWKouTdnukQ67a8aWSBVT6u/LgBpsd+dBIOhH1/g2e9Gtg26InHJluCMrdTJU9Q14aFuaPBijGV3NxhbPXqdT9VyZ038rEjoaMrG+ig6vsEa+HVDtukdIpI= - - secure: r0nnAQtdcU7snnp9467KgbrbrA/LQor+cS8IaLRVr8zXdW+6P4WeaE25ofST04YukX1+TlBHMPw6W1R0NlNUF2Jif1tgNWpN9QvAUBDl72yrZIq5LaRUhJlGjFwJRq9ZbV9SjX99yvsBopfv6pB7CgkXLNOjIshp9QJiYTZ371/qE9NH0+83tkA/rqQpm9cEU8axwtKhtC9WyNBBtWE9f8OdcSXAlSf1eRohs+Rbe/qSMT9MMAVYEuw7tPB/dcFVgo36yOjSF1sr42Kydk2FXEZ06z7ceDld9QkO/BT3qKY9UMiJHzMgAMzXp0mRAW/WYl98l2d+b+6nbmXjIJMQln0ZYOVmJr/hPYOdHxzv6Ik/uE/zeFowtfnpbT7WqN9VmyLb8kfunC9JduzIQB4vdRNlrgyi+DD4yKXGb0fzmAmsb8E9cqrrJ0ek5ofA8OCfjP/NQofPpIHjJchMB/oKO1l/wEYwVKfh37vm48DTZ8PwQKkXE4szW1g0RHVnYfZtgSBTBCQVukpw3PjobyLDXzZtBE/zGW/yvWEXOHtReYC29QTQTPxXXGlTBJl3dVnXrXzE2eRNdrM54/VutVrjMVLKbI/hEdoFbAUL8Zpsax3JkCi7kJ8j9iAaf34XOULjmPpQIwscJyXpH31OrcN43/eJyc87dnnAjfvqJ0gsZk8= - - secure: k3yM6Eeu1bl83wfNsthZqaKBdJa8RtBBGzvaUv52W0m0Iq/aOtsxMBzKRhceyHECXgOci7QdPoSjlxO34qkPl8MOTFGUc6/5duRfhXY2ZZTzCobAIIong7tSJLb16cagbkfWnwMoK+9/Yg8aOB7Dxxpq54nGssjt5oqzA481GMj+zPU9ebILRHJXRESbtZGYTAkb5GbxLoWIJ+8pxZv4JI5WkrFNF84GQAtIjOdOCDXtWXkwMqLGyu9Vnfii+VEB4XJZJdZ/eir3cIB7oZnsd+5E1iEGJ5sHGKh/sF0/RIaC7CR9SQjP8ihuNlku5XmBEmwR1KmRJonnPiiT8iGJOgtCUsQ7u1ofYxIVAoAsDFjD7vMJBXp1jJUwtARp8on1hHzTQ1c8C/c+/rN+aFSKuG/lBtY0yNxy2XcYSsUdxBDgFhWAwoF0dsEjBlIAeH1JdLpJYZ3DOqECKIbiFUXWV8lE3sB1fqtV6pR4Mff3GcZT9btsz6/K4gSiv5DsCSSrb7CoeU4FprfXOfqHtHXyRTyVQwosOr+fNc299h8QQ0BEL2yU40dqnpsCvhQzKfP1iITcGfYRp4cXjBbzU8ZZ5JKoFsh83DOBTcjEzaNHLgvBJ/l7jEd3zDrw+L+F6pE0CRJSLgb5ecUpXMqxj+MqiXqy+xSB2yXvkCAssdTmfuI= - - secure: hunUZmaPhrD1IWzmChMrTjWl2K8XcCu2l0a1XY0vgvmzrLvaaLExTN5Xr3ek9lART1s6qoGQw61GUmGN8FBclX7fxrzLsBVYQuSoVepVmKDyzMVDuRBYoB8f5s23hrAA8QXkQz6iT6n4B8tH0Tc6/wKMuBSYU6kjnNQJYb1heY9ppKTkjM6kwAlWIuUT2GeFG3keL5ZaIWZR/WcAUKUJ74ljweP7PxHvQgrCwOOCkFWMO+bGdS5+gqgjeXQ0WQ+pVh4PqmezVkiff/Nr0uWo2aeB4xcgCa2loOqc8tgYfbPcveyJO1tR64APdwL51ZeyIajxW8iz2VK5wUiIsRexihZYpVG3ykdQpRyLgUWmc8nhPAm/pAdmV+ZrdKPn8bA1D2xefQRb6aB3Ku6f/xXLVsrwNm2MXwyPEA3EDS3x+D7Bybs6Rg/8MbeHs5cTHLN5AKRl/FiF8TR//kMVtsLKj5Aj+n6kY9LMkgEAfObIb28iZdqdc7A6gh53ofiXzmics93Elm/wkGBDEDOf1obw2nMxkgmXlTCJxpwWNnkrNSQ2f1YSj5RRYsQY5BOCRmemv2avgfCSX4GJSsBeQPy9ztH+8dNqjt2k/2VceWh8vveg/TtTQ0TOBdgrtyfpSLHvApMkjzQ0fSwv+Nb/51i8pEBNMzqZhCkbBaOHbS1hHtw= - - secure: EACEddZXExFtO2w2DVnlBwHILVnPDDO1C6ohank5cEtzWusez0uCQTIrNPvbp5aXYHH4EltgQsnJ2jtCCxlxX1Y7Iu+Y5yOaQATVQBJMPrqmgjwCajrEuJTUUyvBwJ53MRx05ikOeMKm6Nc9vZ0/HCn7r4bIbFmVSrJN6QF2wmNtAg2hiER59ln+V2KkmJKjoh5w05uwDJ4ElZtUYZgfDLPZ+nTDjRfZGis0Qc90D5Gee5v8yR27OFexWssWIUdiopdcOmOKm7VCLqXxR9vbvBxEKpYEXWCkbaZFRHnbIE2v8058Y9jOI7pTq5DtzQlrrIXrwXREBTT+XR1Mk9bG4+bWsQGFMSlUuJylXFSBTiT9Ck9DMT1Rr/Dkn7iJ/PTxIEFjd6h2rBfCB0RtELosDbwLPVt0FmoP3oOnXUxS9ytbKkh9ok1uKQSjt6IrIuU3SF+jeD5cnUQ20wWOaMm/8sxgBRDFWKmM3yACFMr78z+5Z8I5WIydgFM6gh8XIZtBVk21S5sSm+YarvciN2AJ/FL0DKAeYARuLbjwFNnMnOaqn8bZ0IyRTd2qjWXq4vbbxyE6zttxfwId2+93m0bxkTPgzpFMNpg+2hSZa/jcjvZ2piuGOfLiBlukrp4ntAAVxgFgORyhYV4h4tmiXA32zFsEAjQATqxUUurF/zaDmCE= - - secure: O3w9lswdKxL2kya9avjaGE4N2oKWaFsGwoM1Ub/pte5DN2Lg0HSDWIw8i3Qalg6xg08UlkSV401FjDwz4W1ykuxHmkl5TvtJ2IhANSFf0rK1LgBXwmF+w2Qqj5bSagJlWQjwyOxWT0KAQqwDaQp01KhOceQnTnRU4R9NYoRJmT8/EL8X0Fz8UzJh/tXhAqF+p07mLUNZOPGzgSQ4neuHg8KF3XNOGEXSOEPEjz/4mz65zm3LI8nQLOAgPocW5IjUPUJaGjuKWdz5GG0wirhMhr8GSdoCzzy32vmOYIG4kksN2VI6ZndmLjH3TdQ5XFzGiQzA46IsIfapXJgzDjuRUaupJhS/4kiZ78P7lJPE8F84XHm+DbobSIm7c4hRJDY+/LneSIRqO8/AO0FpF6HP7Ygx5eXyaWU2grLgTFh5tyFYRESxxBbKGhn6oeaawsjFn9B+7hxyYX1UO6+2EmwqRkY86hw8inH8p6o9XrrXWAMVIAqRNc0EyHaLAKU2utNulQ1VDN+FwyF1tGprebdZ1t0z5bRyLyE1wolzwkc6oNpTyNKjlTPXa0VzYAGYuPJJ6oua1NuHWK0mSTPHo/XhA9StClPjp016WfcSnyGnAkgUeZr6HKWiDS5qwet7G3VD+MO1CKfdgKp/httq3W7ycz9bvlTLR80jdRLZnUdOv5w= + - secure: ipAPhJPN2FxdEigCMEhnxC6VnKwHsPxiG8CIlD9tLBmSBuCuIRe4C7r9lrUbLwDEYLXYUy7K0l9V/t4XTpK6uOFKrSfeBltndjcVXUB7ISsM/cZ/sLRUxwy4fdICZXV2haBTOLViQ+I82si5pr8ycs6b4kXuZjrAbN3BeF9FN8CZbNrJZz4JT3sUEXlDNo7Sa8Ld8uxraBWu/gFeJcHF5ZZcAQJtde59j3/BMtLRlv4jjHVdg1kDxrH5hXk521w1SKvPhDsLhHmCaayEuYW6wxt0sjRep9W4WRDLnZnfMdxn3uwYtd0o1FZiJ6uL+U/p106KaMd0x+J1Ua4pyqHqGO8SgGwdPICsprxE7+bBCqMEiFpSvR/vwv9MjE6pdEtCLnt7kmX4UHQ8A7GunqAu33d1Sh9LSzYXQeReToa3Pel27BMKRzLjO0j8rPQoEBLAZGdrMWV8/j0cbNEoxZYue1kbhn1z96j1Ze0T8quNNpIfi1lhCCWws7zrewbiTnECiwC6gUlcLqz6jJevwMNKnMMMLdbIdKtHQy8dn++LEsUS9FmdxFnorIAssNVcVLaorM+3HRGS555fGyxNyN+OkizclxmgDHeJvJYRwpVVMnE2DZcd3mfVsFEjH+SX5BEOjD/6eRzo64r9nyXluekM4mArPoRRS3tswCAwObSUUyc= + - secure: bqleUgtd2ZyOqzGgnkl2mYSuMcEhbYGsfIVW1r8XduNh2bRQRms9n1GXAyUVkml5PbIWjBtZvdNoZaO5glA55YiJdAjLN4wPpR8aF8EDS8ArkuFBmNalWSIEHjEhAJeboTARn7JnbrpgPe6jVNKIAhtBxBHFD5bDVwlaCbQ3Cs5mS1eLDicSHBOU5JwCL7a7CMlN+RQVvNt88CefYCBdxar+0HXRytYlaqwAZCuuFVaidhrqVmhk5OzyNHhNVk88fRTv7rpz/Jg62jm18i9jtAJJJE5Xp5jV6lws9RuBuWXb+Dw3aeDrm1wwPdwNOEKqfxA/VVcTwtfjrwY3ZjCtK1xalkbLX3wywesNr0eKNSzpZjd8b3OWMLCzMcI5HeAzxhwJEm7p6lkbyptmLd0UX3BmmP214U57nCCBYVohIyJyJgeshZbQlWtRJQN3SGVk7fj+8G1+70fsLLAl5gOQXaEJOyxG8uGbZEzNFVIz2oRjaO2A0Spp0SCY7nVHRbFiinhHZfEtrRontsbSe2SS72myL4lCsl1Hg0Pv8XzKBmEfwot2Xn+4GMsJTLie6ML3F6QAekuSJZmH8KlEUnivHSIiavJHFRohYMhyNtqENquwn+yBWkyHp3bzfpsyJQmzY3UvG+auiINBeUFzNvl+xKB9wBXRtxks/dBtjeEVOHI= + - secure: T6a0YVzI8eo2WdC7xLNpej0E8QlnHPSpU+ClZK1adYlQ8lVOexa3fwsXT0tN6mRVsUSkdbAuLuOGoikt3tJN/FXnabt0YL5tR/VPEc4wAcc/rYrGEoZIzXO2GV1YKSRXildtin2h9bYvzx+cfsDKYp5BhnHoD+Rk7X9xG7pOmRH4bBKZb9KFvFY27yoEBOzZjJOGkfWkSZj2Ef9GkX7q7p5/GkDU3JAuj7nqet79Jt6difxQMCoEX/vxbmiS4MCazsuo8Z0eKHlCcImu29D1aaBQ8mv5pCotFUqYi32qY3Bnx76knRRlotEV5HUFGHx/zP5d9LtEdbv8WAunAYMqPWeh5V8uqUjcUi7AxVIJMOcDzprEA37ahL/YrlPTbOH+RqAKC8ueQAom/yRY3TBPHvFkUAHqHqNIzziOk4Xa6maXMMW79pdOYNIaKn4DgXz2qz8tQJhVUczz+fmD1wkai/sdfnZO9L/0aQw5mAKuz5/5e3/eABh8Gcbkqd8bpmZz/a02hVMzKNU2t6I/DUrZGl6ZfR7pxStf+ylSjMv9E+d8DAoWt2GlR0+rYHTKtERMzNDSaVB72X/oIxr2Un2dfx+jR1/SN2FEyByWOXVap64Vs8XtrMl+80hwvVbrpiuTS57m1GgTuqTnEvCQd00tRxbyohNF0unBZ/5jeWgvvOQ= + - secure: Kk/wqyVDvosbYLSk9FoP8V4xNPKRDjDCk8T8vr7g/IhOKLqvONiYl7j3cUaXAeSTWX8CE0bp+EN2hVC2dgurFA6RJqr8jVaNSJDhdZUQ4U3I8dtqgt8+CXkAVfrwNlnaaeYq+ZskPDFGcB5U6MiJZtrXQUx8Lm1sDSs9/03DjvXvsFLSqhXm4eTSChLUW/AEOxw8IibIDebDyhRi6e5s292WRkVNEe483ZBAot2pWU8qa/8nlLlMvZICL0avaNbm1ufzMFuz+wuQGbDJXJGFn2AIHOhUV9D5ZBo8umJNyextK0qo1NeUqZufj+GiVgT3UNYHpyLTRjcemQhcegtbnJ7tkokWZfA8qSb3A7ws68ppqGw/CUZ5juQsXvw+qCWzM5qaXQz1HQmrPSdHYtJTMhl6N72XuEAzDMYh3ZHFpTitAIooc+6a0ENtToj2npOtB/ypHbQWt6O171cPjd/URTnxjvnCrw0aQynQd7V8XVHsyQnz/+pKDWMkpeLUn9nG+qEjGb2MEg5lVmxpUmP9iI3QC4gd+px83W5y3ymlRavdGVdgB95jDHarSxlz6uIjHRBGsEGgmkyNcB12YLl0UgcmgRIIp/i7I37ecdaek4gKpY2+IupTR0lbcF4HTrMDR3UmGGSM/3pq5faAfIX1BqsgSlWO6Iz6qkilDVYWFUY= + - secure: r8GPUh8GzRjPQzlOGLntqX45Fm6uMxcvE9bCK9XlIrvA3akL5jRpspg9ud6ALvNQiXy7o+stRQLnHMAwi2s+So1IO3IRT5B7ytN39fr/5ChMBg0MPD6NM9XeSSAgkQ5POtpytdbV/8X/Z/R/3NwnhqYPXyX+h/exLV5Vvp+wm5s7ts1VDiuWXyzogxP3lAQL0GzsrP2leI7ptv/VPYxkV0aTZUM7qKWXD9y8tutSWq6ZnWAqMwMvxjvM54lvrfre80KD+0nm6V/80i3W9+YNE1VJARvhKCcNUUBav2gl7I6lt2z8tBNbl3x3Y4fUiKsBYv8cwj1bIzI2O+s6455CECBvohlm2VzZIc4J5Sw/Pox00/6aRfZvFu7sv44/l/qXlDuCdHbWpN0G34ALeVEHJA4B+QBJU5n6fW07aA26zgi4vWGquhPhhXOnQx++LfDMEaOf7YbcGPLe4yQ34n3G90LPY35AgB6AxQZjA4nf366CC6fOE7QlopJtfziXoh1KNgiaqkWS2Sy32SwP8sue/8qL+52K2obVXRlapFGZlZVAKO9RVSBMXXjzASehfaJn1Gc9Ns3aTnK5qZNU3Pe/CMgkOpmi2oswackivnl4/kR7RbmRtWp5AWy6ihWMl9xrDCsk/k/47VKhbOPTMXnyZwxAL863n23MHGvQ4z6h1zg= + - secure: AOEjis+8Xp11iTnUh1+9qJ5M8P+bkBHjFLn6TTGIiT7rA1+w08ktgRUy7dNYmy6/Mck8jngq69utTlR2xMd95a1Gbx2mCoZQZj1veq/J6Y9qnQxLqumH+KzjlMNRRGJ5wYl2BCKtAVC27ZC6zYDqGcLLbzr1PgeNLRTJRjQI6Hv0AzLLKi6vqvnSGDL9u2SLz7MnbtQrVvHH5Z+gP/xlXr7aZCepT0+r+gL34vzNyojUnwqfKLKgm3Vm7r41YDoQCvkGv3YKNJijPXkz84TQIAY0ghktH5LGBW6RobS8Vm+wnG2iIckcuTqI6jz7eTrOJn5IuQ87c92gia7lehncVDWvFb7dwjjSFH0Z3WP2dA+EDreTjMBxTwPGXpVMZWR3h3cXJ6dFLfcRxfqkgkwINGM4+3bL6RFEIthJnWG/xUnJ2dKylix9ouz8ehh9Mv9WlwOKEzL0YJX4RN/t5YheZ67b05Mi/Ydq/loLWgv3NIebFAbHfCDTcaGSOHJ8cNdagUWaWZvphDP0mf3xgvei+CuRu37hMuRe4fRZtHdvKRa2DL9GzupbPK6I21pwH3cQak991IuRKgGZA1kiHovIx2kDGACPfj4SKZCRr6q8wnkvMLTVEcwMKggwE6daQ3yn5G77dQjgaK8y024yQCOqz0su500IgG+B6ITPnq0viEc= + - secure: vL0Zf+rvVDg/3jlbUv9GRPgEIugmDKDmYFDZHrsDCY7SMwVDC9PNaWhUKZTmGb+cl9XPXeMjm4ex2kUW5fXeo48SOtqfEOsudCMGCTwQuJYmBDSVZx9tlNtHMOxfQR/9bbW3UwxWinlMMmXnMLRWASwOjyJaYwjFZBdppm06BpuTCMNocLvyT4KVt7afM24WnsY2iajVFwmAhHBb+ZbkltU4tR/+xpq7zEuQzdQJjmOQtvvY6nnhxMi7/CMIcL9qzg/YOBZHhrtQrYRfIMDSH88ZzQRc859Km32i/NKg2ZxuaGAh/OZjdN0YVU+aZopSL/zTUMeOCN7+a7sE8B1SA1XJhYdd7OYEwRQZbHvP6JP0oEIzZb3AdURgXNR9pBHlYf0iOnLVq0QJv24wRV665fiokwirnCU7RXLTFw3NpYT9qZaIuRPdJ81k8/tVP4ypFJ8uD1IlZwGscolbpExUf1G1KTuc9v8pIk1MFHFSC7WUhL8rzMF23eLiYVpwHTlLaMyMqoMoPdyRg6boSnRjai+TdD3hOjFXq8NZrTPewjXUXk4WeuZhR8OVk4dVVERZF2dG/IZE09Q5P/nFh4qzRriE8/skqoXYukEAx58K+6D4bh6+R2JdOOuwK1XkqC7B/PBcJkxsxYZF+u3IXO2xF7wa/VUgLFBcvYhlJ/n2P2E= From 8eb20f8ba460cf0685092516f3a684dc80528e97 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 23 Jun 2017 16:13:21 +0200 Subject: [PATCH 10/11] Release 1.1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0345b3d..d263165 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.1.1.BUILD-SNAPSHOT" +version "1.1.1" group "org.grails.plugins" apply plugin:"eclipse" From 19b5e419a855576b05ec7615e890993a49635243 Mon Sep 17 00:00:00 2001 From: graemerocher Date: Fri, 23 Jun 2017 16:13:45 +0200 Subject: [PATCH 11/11] back to snapshot --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d263165..eb10e95 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.1.1" +version "1.1.2.BUILD-SNAPSHOT" group "org.grails.plugins" apply plugin:"eclipse" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy