Skip to content

Commit c3616d1

Browse files
committed
Upgrade to RxJava 1.2.7 / Refine observable handling
1 parent dcf061e commit c3616d1

File tree

9 files changed

+118
-71
lines changed

9 files changed

+118
-71
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ dependencies {
5050
compile "org.springframework.boot:spring-boot-starter-tomcat"
5151
compile "org.grails:grails-dependencies"
5252
compile "org.grails:grails-web-boot"
53-
compile 'io.reactivex:rxjava:1.2.6'
53+
compile 'io.reactivex:rxjava:1.2.7'
5454

5555
console "org.grails:grails-console"
5656

src/docs/asciidoc/gettingStarted.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ You then may want to include an implementation of RxGORM, for the examples in th
1616
----
1717
dependencies {
1818
...
19-
compile 'org.grails.plugins:rx-mongodb:6.0.0.M2'
19+
compile 'org.grails.plugins:rx-mongodb:6.0.7'
2020
}
2121
----
2222

src/docs/asciidoc/serverSentEvents.adoc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ For example:
88
----
99
def index() {
1010
11-
rx.stream { Subscriber subscriber -> <1>
11+
rx.stream { rx.Observer observer -> <1>
1212
for(i in (0..5)) {
1313
if(i % 2 == 0) {
14-
subscriber.onNext(
14+
observer.onNext(
1515
rx.render("Tick") <2>
1616
)
1717
}
1818
else {
19-
subscriber.onNext(
19+
observer.onNext(
2020
rx.render("Tock")
2121
)
2222
@@ -28,7 +28,7 @@ def index() {
2828
}
2929
----
3030

31-
<1> Call the `stream` method passing a closure that accepts an `rx.Subscriber` to start sending events
31+
<1> Call the `stream` method passing a closure that accepts an `rx.Observer` to start sending events
3232
<2> Emit a one or many items using `onNext`
3333
<3> Call `sleep` to simulate a slow request
3434
<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
6060

6161
[source,groovy]
6262
----
63-
stream "ticktock", { Subscriber subscriber ->
63+
stream "ticktock", { rx.Observer observer ->
6464
----
6565

6666
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:
8080

8181
[source,groovy]
8282
----
83-
rx.stream { Subscriber subscriber ->
83+
rx.stream { rx.Observer observer ->
8484
for(i in (0..5)) {
8585
if(i % 2 == 0) {
86-
subscriber.onNext(
86+
observer.onNext(
8787
rx.event("Tick", event:"Event $i", id:"$id", comment:"Tick Event") <1>
8888
)
8989
}

src/main/groovy/grails/rx/web/Rx.groovy

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package grails.rx.web
22

3-
import grails.async.Promises
43
import grails.web.databinding.DataBindingUtils
54
import grails.web.mapping.mvc.exceptions.CannotRedirectException
65
import groovy.transform.CompileDynamic
@@ -17,10 +16,11 @@ import org.grails.web.servlet.mvc.GrailsWebRequest
1716
import org.springframework.web.context.request.RequestContextHolder
1817
import org.springframework.web.context.request.async.WebAsyncManager
1918
import org.springframework.web.context.request.async.WebAsyncUtils
19+
import rx.Emitter
2020
import rx.Observable
2121
import rx.Subscriber
22+
import rx.functions.Action1
2223

23-
import javax.servlet.ServletInputStream
2424
import javax.servlet.http.HttpServletRequest
2525
import java.util.concurrent.TimeUnit
2626

@@ -223,36 +223,36 @@ class Rx {
223223
*/
224224
@CompileDynamic
225225
static Observable bindData(Object object, Object bindingSource, Map arguments = Collections.emptyMap(), String filter = null) {
226-
Observable.create( { Subscriber<? super Object> subscriber ->
227-
subscriber.onStart()
228-
Promises.task {
229-
if(bindingSource instanceof HttpServletRequest) {
230-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource)
231-
// horrible hack, find better solution
232-
GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null
233-
if(webRequest != null) {
234-
RequestContextHolder.setRequestAttributes(webRequest)
235-
try {
236-
List includeList = convertToListIfCharSequence(arguments?.include)
237-
List excludeList = convertToListIfCharSequence(arguments?.exclude)
238-
239-
DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter)
240-
} finally {
241-
RequestContextHolder.setRequestAttributes(null)
242-
}
243-
}
244-
else {
245-
object.properties = bindingSource
226+
Observable.create( { Emitter<? super Object> emitter ->
227+
try {
228+
if(bindingSource instanceof HttpServletRequest) {
229+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource)
230+
// horrible hack, find better solution
231+
GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null
232+
if(webRequest != null) {
233+
RequestContextHolder.setRequestAttributes(webRequest)
234+
try {
235+
List includeList = convertToListIfCharSequence(arguments?.include)
236+
List excludeList = convertToListIfCharSequence(arguments?.exclude)
237+
238+
DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter)
239+
} finally {
240+
RequestContextHolder.setRequestAttributes(null)
246241
}
247242
}
248243
else {
249244
object.properties = bindingSource
250245
}
251-
subscriber.onNext(object)
252-
subscriber.onCompleted()
253-
254246
}
255-
} as Observable.OnSubscribe<Object>)
247+
else {
248+
object.properties = bindingSource
249+
}
250+
emitter.onNext(object)
251+
emitter.onCompleted()
252+
} catch (Throwable e) {
253+
emitter.onError(e)
254+
}
255+
} as Action1<Emitter<InputStream>>, Emitter.BackpressureMode.NONE)
256256
}
257257

258258

@@ -264,24 +264,28 @@ class Rx {
264264
* @return An observable
265265
*/
266266
static Observable<InputStream> fromBody(HttpServletRequest request) {
267-
Observable.create( { Subscriber<InputStream> subscriber ->
268-
subscriber.onStart()
269-
Promises.task {
270-
InputStream inputStream = null
267+
Observable.create( { Emitter<InputStream> subscriber ->
268+
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request)
269+
if(!asyncManager.isConcurrentHandlingStarted()) {
270+
throw new IllegalStateException("Servlet async processing must have been started to use fromBody(..)")
271+
}
272+
InputStream inputStream = null
273+
try {
271274
try {
272-
try {
273-
inputStream = request.getInputStream()
274-
subscriber.onNext(inputStream)
275-
} catch (Throwable e) {
276-
subscriber.onError(e)
277-
}
278-
} finally {
275+
inputStream = request.getInputStream()
276+
subscriber.onNext(inputStream)
279277
subscriber.onCompleted()
278+
} catch (Throwable e) {
279+
subscriber.onError(e)
280+
}
281+
} finally {
282+
try {
280283
inputStream?.close()
284+
} catch (Throwable e) {
285+
// ignore
281286
}
282-
283287
}
284-
} as Observable.OnSubscribe<InputStream>)
288+
} as Action1<Emitter<InputStream>>, Emitter.BackpressureMode.NONE)
285289
}
286290

287291
/**

src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package org.grails.plugins.rx.web
22

33
import groovy.transform.CompileStatic
4-
import rx.Observable
5-
import rx.Subscriber
4+
import rx.Observer
65

76
import java.util.concurrent.TimeUnit
87

@@ -21,9 +20,9 @@ class NewObservableResult<T> extends TimeoutResult {
2120
super(timeout, unit)
2221
this.callable = callable
2322
def parameterTypes = this.callable.parameterTypes
24-
boolean isSubscriber = parameterTypes.length == 1 && Subscriber.isAssignableFrom(parameterTypes[0])
23+
boolean isSubscriber = parameterTypes.length == 1 && Observer.isAssignableFrom(parameterTypes[0])
2524
if(!isSubscriber) {
26-
throw new IllegalArgumentException("Passed closure must accept argument of type rx.Subscriber")
25+
throw new IllegalArgumentException("Passed closure must accept argument of type rx.Observer")
2726
}
2827
}
2928

src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ import org.springframework.beans.factory.annotation.Autowired
1515
import org.springframework.web.context.request.async.AsyncWebRequest
1616
import org.springframework.web.context.request.async.WebAsyncManager
1717
import org.springframework.web.context.request.async.WebAsyncUtils
18+
import rx.Emitter
1819
import rx.Observable
1920
import rx.Subscriber
21+
import rx.functions.Action1
22+
import rx.schedulers.Schedulers
2023

2124
import javax.servlet.AsyncContext
2225
import javax.servlet.ServletResponse
@@ -117,18 +120,22 @@ class RxResultTransformer implements ActionResultTransformer {
117120

118121
// in a separate thread register the observable subscriber
119122
asyncContext.start {
120-
observable.subscribe(subscriber)
123+
observable.observeOn(Schedulers.immediate()) // run on the async servlet thread
124+
.subscribe(subscriber)
121125
}
122126
}
123127
else {
124128
asyncContext.start {
125129
NewObservableResult newObservableResult = (NewObservableResult)actionResult
126-
Observable newObservable = Observable.create( { Subscriber newSub ->
130+
131+
Observable newObservable = Observable.create( { Emitter newSub ->
127132
Closure callable = newObservableResult.callable
128133
callable.setDelegate(newSub)
129134
callable.call(newSub)
130-
} as Observable.OnSubscribe)
131-
newObservable.subscribe(subscriber)
135+
} as Action1<Emitter>, Emitter.BackpressureMode.NONE)
136+
newObservable
137+
.observeOn(Schedulers.immediate()) // run on the async servlet thread
138+
.subscribe(subscriber)
132139
}
133140
}
134141
// return null indicating that the request thread should be returned to the thread pool

src/test/groovy/org/grails/plugins/rx/EventSpec.groovy

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import org.grails.web.servlet.mvc.GrailsWebRequest
1313
import org.grails.web.util.GrailsApplicationAttributes
1414
import org.springframework.mock.web.MockHttpServletRequest
1515
import org.springframework.web.context.request.RequestContextHolder
16+
import rx.Emitter
17+
import rx.Observer
1618
import rx.Subscriber
1719
import spock.lang.Specification
1820

@@ -126,23 +128,23 @@ class EventController implements Controller, RestResponder{
126128
RxHelper rx = new RxHelper()
127129

128130
def stream() {
129-
rx.stream { Subscriber subscriber ->
131+
rx.stream { Observer observer ->
130132
for(i in 0..3) {
131-
subscriber.onNext(
133+
observer.onNext(
132134
rx.event("Foo $i\nFoo\nBar\nBaz", event: "Event $i", comment: 'potato', id: "$i")
133135
)
134136
}
135-
subscriber.onCompleted()
137+
observer.onCompleted()
136138
}
137139
}
138140

139141
def streamJson() {
140-
rx.stream { Subscriber subscriber ->
142+
rx.stream { Emitter emitter ->
141143
for(i in 0..3) {
142144
def foo = """bar $i
143145
bar $i"""
144146
def json = [foo: foo, 'baz': 3] as JSON
145-
subscriber.onNext(
147+
emitter.onNext(
146148
rx.event(new Writable() {
147149
@Override
148150
Writer writeTo(Writer writer) throws IOException {
@@ -151,7 +153,7 @@ bar $i"""
151153
}, id: "$i", retry: 1000)
152154
)
153155
}
154-
subscriber.onCompleted()
156+
emitter.onCompleted()
155157
}
156158
}
157159
}

src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.grails.web.servlet.mvc.GrailsWebRequest
1414
import org.grails.web.util.GrailsApplicationAttributes
1515
import org.springframework.mock.web.MockHttpServletRequest
1616
import org.springframework.web.context.request.RequestContextHolder
17+
import rx.Emitter
1718
import rx.Subscriber
1819
import spock.lang.Specification
1920
import static grails.rx.web.Rx.*
@@ -119,35 +120,35 @@ data: {"foo":"bar 3"}
119120
}
120121
class NewObservableController implements Controller, RestResponder{
121122
def index() {
122-
create { Subscriber subscriber ->
123-
subscriber.onNext(
123+
create { Emitter emitter ->
124+
emitter.onNext(
124125
render("Foo")
125126
)
126-
subscriber.onCompleted()
127+
emitter.onCompleted()
127128
}
128129
}
129130

130131
def stream() {
131-
stream { Subscriber subscriber ->
132+
stream { Emitter emittter ->
132133
for(i in 0..3) {
133-
subscriber.onNext(
134+
emittter.onNext(
134135
render("Foo $i")
135136
)
136137
}
137-
subscriber.onCompleted()
138+
emittter.onCompleted()
138139
}
139140
}
140141

141142
def streamJson() {
142-
stream { Subscriber subscriber ->
143+
stream { Emitter emitter ->
143144
for(i in 0..3) {
144-
subscriber.onNext(
145+
emitter.onNext(
145146
render(contentType:"application/json") {
146147
foo "bar $i"
147148
}
148149
)
149150
}
150-
subscriber.onCompleted()
151+
emitter.onCompleted()
151152
}
152153
}
153154
}

src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,44 @@ class RenderSpec extends Specification {
131131
RequestContextHolder.setRequestAttributes(null)
132132

133133
}
134+
135+
136+
void "Test render a from body async"() {
137+
setup:
138+
GrailsWebRequest webRequest = GrailsWebMockUtil.bindMockWebRequest()
139+
MockHttpServletRequest request = webRequest.getCurrentRequest()
140+
request.setAsyncSupported(true)
141+
RenderController controller = new RenderController()
142+
request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller)
143+
request.setContent("Foo".bytes)
144+
when:"A controller uses the render method and a string"
145+
Observable observable = controller.renderFromBody()
146+
147+
then:
148+
observable != null
149+
150+
when:"The observable is transformed"
151+
RxResultTransformer transformer = new RxResultTransformer()
152+
def result = transformer.transformActionResult(webRequest, "renderText", observable)
153+
then:"null is returned"
154+
result == null
155+
webRequest.response.contentAsString == "Foo"
156+
157+
cleanup:
158+
ConvertersConfigurationHolder.clear()
159+
RequestContextHolder.setRequestAttributes(null)
160+
161+
}
134162
}
135163

136164
class RenderController implements Controller {
137165

166+
def renderFromBody() {
167+
fromBody(request)
168+
.map { InputStream input ->
169+
render(input.text)
170+
}
171+
}
138172
def renderView() {
139173
Observable.just("Foo")
140174
.map { String result ->

0 commit comments

Comments
 (0)
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