14
14
*/
15
15
package crud .implementer ;
16
16
17
- import java .util .concurrent .Callable ;
18
17
import java .util .concurrent .ExecutorService ;
19
18
import java .util .concurrent .Executors ;
20
- import java .util .concurrent .RejectedExecutionException ;
21
19
import java .util .concurrent .TimeUnit ;
22
20
import java .util .concurrent .TimeoutException ;
23
21
import java .util .concurrent .atomic .AtomicBoolean ;
28
26
import crud .core .Session ;
29
27
import rx .Observable ;
30
28
import rx .Observer ;
29
+ import rx .Scheduler ;
31
30
import rx .Subscriber ;
31
+ import rx .functions .Action1 ;
32
+ import rx .schedulers .Schedulers ;
32
33
33
34
34
35
/**
@@ -41,43 +42,81 @@ public final class SessionWorker {
41
42
42
43
private final AtomicBoolean stopped = new AtomicBoolean (false );
43
44
private final ExecutorService executor = Executors .newSingleThreadExecutor ();
45
+ private final Scheduler scheduler = Schedulers .from (this .executor );
44
46
45
47
46
48
/**
47
- * Schedule the given task to run on this worker's asynchronous thread.
48
- * Return a {@link Observable#cache() cached} {@link Observable} that
49
- * will emit the success or error result of the task to all subscribers.
50
- * <p/>
51
- * If {@link #shutdown(Callable, long, TimeUnit)} was already called, the
52
- * resulting {@link Observable} will emit
53
- * {@link RejectedExecutionException}.
49
+ * Wrap the given {@link Action1 action} in an {@link Observable} and
50
+ * schedule its subscription to run on the {@link Scheduler} encapsulated
51
+ * by this {@link SessionWorker worker}. This method only creates the
52
+ * {@link Observable}; it does not subscribe to it.
53
+ *
54
+ * @see #subscribeHot(Observable)
54
55
*/
55
- public Observable <Void > submit (@ Nonnull final Callable <Void > task ) {
56
- if (!this .stopped .get ()) {
57
- return doSubmit (task );
58
- } else {
59
- return Observable .error (new RejectedExecutionException ("Already stopped" ));
60
- }
56
+ public <T > Observable <T > scheduleCold (@ Nonnull final Task <T > task ) {
57
+ final Observable .OnSubscribe <T > onSubscribe = new Observable .OnSubscribe <T >() {
58
+ @ Override
59
+ public void call (final Subscriber <? super T > sub ) {
60
+ try {
61
+ task .call (sub );
62
+ sub .onCompleted ();
63
+ } catch (final MiddlewareException mx ) {
64
+ sub .onError (mx );
65
+ } catch (final Exception ex ) {
66
+ sub .onError (new MiddlewareException (ex .getMessage (), ex ));
67
+ }
68
+ }
69
+ };
70
+ return Observable .create (onSubscribe ).subscribeOn (this .scheduler );
71
+ }
72
+
73
+ /**
74
+ * Wrap the given {@link Action1 action} in an {@link Observable} and
75
+ * schedule its subscription to run on the {@link Scheduler} encapsulated
76
+ * by this {@link SessionWorker worker}. Then
77
+ * {@link Observable#cache() cache} the result, and begin the
78
+ * subscription. This allows the subscription action to begin immediately,
79
+ * but allows the caller to see every resulting value. Note that the use
80
+ * of cache() assumes that the number of those results is relatively
81
+ * small.
82
+ *
83
+ * @see #scheduleCold(Task)
84
+ * @see #subscribeHot(Observable)
85
+ */
86
+ public <T > Observable <T > scheduleHot (@ Nonnull final Task <T > task ) {
87
+ final Observable <T > result = scheduleCold (task ).cache ();
88
+ doSubscribeHot (result );
89
+ return result ;
90
+ }
91
+
92
+ /**
93
+ * Start subscribing to the given {@link Observable} on the
94
+ * {@link Scheduler} encapsulated by this {@link SessionWorker worker}.
95
+ * Note that the caller may miss some results if it does not e.g.
96
+ * {@link Observable#share() share} the input Observable before calling
97
+ * this method.
98
+ */
99
+ public <T > void subscribeHot (final Observable <T > obs ) {
100
+ doSubscribeHot (obs .subscribeOn (this .scheduler ));
61
101
}
62
102
63
103
/**
64
- * Stop accepting new tasks via {@link #submit(Callable)}, and initiate
104
+ * {@link #scheduleHot(Task) Schedule} the given
105
+ * task, then stop accepting any new tasks, and initiate
65
106
* the termination of this worker's background thread. The resulting
66
107
* {@link Observable} will emit {@link Observer#onCompleted()} once the
67
108
* termination is complete, a {@link TimeoutException} if it fails to
68
109
* complete within the given duration, or {@link InterruptedException} if
69
110
* the shutdown is interrupted.
70
111
*
71
112
* @param finalTask The caller should perform any of its own cleanup in
72
- * this task, scheduled here as opposed to in
73
- * {@link #submit(Callable)} to avoid race conditions on
74
- * shutdown.
113
+ * this task, scheduled here to avoid race conditions.
75
114
*/
76
115
public Observable <Void > shutdown (
77
- final @ Nonnull Callable <Void > finalTask ,
116
+ @ Nonnull final Task <Void > finalTask ,
78
117
final long waitDuration , @ Nonnull final TimeUnit waitUnit ) {
79
118
if (!this .stopped .getAndSet (true )) {
80
- final Observable <Void > taskResult = doSubmit (finalTask );
119
+ final Observable <Void > taskResult = scheduleHot (finalTask );
81
120
this .executor .shutdown (); // non-blocking
82
121
final Observable <Void > await = Observable .create (new Observable .OnSubscribe <Void >() {
83
122
@ Override
@@ -112,53 +151,23 @@ public void call(final Subscriber<? super Void> sub) {
112
151
}
113
152
}
114
153
115
- private Observable <Void > doSubmit (final Callable <Void > task ) {
116
- final Observable <Void > result = Observable .create (actionOf (task )).cache ();
117
- /* Start a subscription now, so that the given task is immediately
118
- * scheduled. The no-argument subscribe() does not handle errors, so
119
- * materialize so that it won't see any.
154
+ private static <T > void doSubscribeHot (final Observable <T > obs ) {
155
+ /* The no-argument subscribe() does not handle errors, so
156
+ * materialize() so that it won't see any.
120
157
*/
121
- result .materialize ().subscribe ();
122
- return result ;
158
+ obs .materialize ().subscribe ();
123
159
}
124
160
125
- private Observable .OnSubscribe <Void > actionOf (final Callable <Void > task ) {
126
- return new Observable .OnSubscribe <Void >() {
127
- @ Override
128
- public void call (final Subscriber <? super Void > sub ) {
129
- SessionWorker .this .executor .submit (runAndNotify (task , sub ));
130
- }
131
- };
132
- }
133
161
134
- private static Runnable runAndNotify (
135
- final Callable <Void > runMe ,
136
- final Subscriber <? super Void > notifyMe ) {
137
- return new Runnable () {
138
- @ Override
139
- public void run () {
140
- try {
141
- runMe .call ();
142
- notifyMe .onCompleted ();
143
- } catch (final RuntimeException rex ) {
144
- /* RuntimeExceptions are assumed to indicate program bugs.
145
- * Report them to the subscriber as-is. This clause will
146
- * also include MiddlewareExceptions as-is, which is
147
- * desirable.
148
- */
149
- notifyMe .onError (rex );
150
- } catch (final Exception ex ) {
151
- /* Any checked exception is assumed to represent a
152
- * middleware-specific failure condition, and so is mapped
153
- * to MiddlewareException.
154
- *
155
- * TODO: Provide a pluggable exception-translation
156
- * capability.
157
- */
158
- notifyMe .onError (new MiddlewareException (ex .getMessage (), ex ));
159
- }
160
- }
161
- };
162
+ /**
163
+ * An asynchronous task to be scheduled by a {@link SessionWorker}.
164
+ * It will be wrapped by an instance of {@link rx.Observable.OnSubscribe},
165
+ * and execution of {@link Observer#onCompleted()} and
166
+ * {@link Observer#onError(Throwable)} will be taken care of on behalf of
167
+ * the task; it need only invoke {@link Observer#onNext(Object)}.
168
+ */
169
+ public static interface Task <T > {
170
+ void call (Subscriber <? super T > sub ) throws Exception ;
162
171
}
163
172
164
173
}
0 commit comments