15
15
import com .apollographql .apollo .subscription .SubscriptionTransport ;
16
16
import org .jetbrains .annotations .NotNull ;
17
17
18
- import java .util .ArrayList ;
19
18
import java .util .Collection ;
20
19
import java .util .Collections ;
21
20
import java .util .LinkedHashMap ;
22
21
import java .util .List ;
23
22
import java .util .Map ;
24
23
import java .util .Timer ;
25
24
import java .util .TimerTask ;
25
+ import java .util .UUID ;
26
26
import java .util .concurrent .CopyOnWriteArrayList ;
27
27
import java .util .concurrent .Executor ;
28
28
import java .util .concurrent .TimeUnit ;
@@ -37,7 +37,7 @@ public final class RealSubscriptionManager implements SubscriptionManager {
37
37
static final long CONNECTION_ACKNOWLEDGE_TIMEOUT = TimeUnit .SECONDS .toMillis (5 );
38
38
static final long INACTIVITY_TIMEOUT = TimeUnit .SECONDS .toMillis (10 );
39
39
40
- Map <String , SubscriptionRecord > subscriptions = new LinkedHashMap <>();
40
+ Map <UUID , SubscriptionRecord > subscriptions = new LinkedHashMap <>();
41
41
volatile SubscriptionManagerState state = SubscriptionManagerState .DISCONNECTED ;
42
42
final AutoReleaseTimer timer = new AutoReleaseTimer ();
43
43
@@ -82,8 +82,7 @@ public RealSubscriptionManager(@NotNull ScalarTypeAdapters scalarTypeAdapters,
82
82
}
83
83
84
84
@ Override
85
- public <T > void subscribe (@ NotNull final Subscription <?, T , ?> subscription ,
86
- @ NotNull final SubscriptionManager .Callback <T > callback ) {
85
+ public <T > void subscribe (@ NotNull final Subscription <?, T , ?> subscription , @ NotNull final SubscriptionManager .Callback <T > callback ) {
87
86
checkNotNull (subscription , "subscription == null" );
88
87
checkNotNull (callback , "callback == null" );
89
88
dispatcher .execute (new Runnable () {
@@ -111,11 +110,15 @@ public void run() {
111
110
*/
112
111
@ Override
113
112
public void start () {
113
+ final SubscriptionManagerState oldState ;
114
114
synchronized (this ) {
115
+ oldState = state ;
115
116
if (state == SubscriptionManagerState .STOPPED ) {
116
- setStateAndNotify ( SubscriptionManagerState .DISCONNECTED ) ;
117
+ state = SubscriptionManagerState .DISCONNECTED ;
117
118
}
118
119
}
120
+
121
+ notifyStateChanged (oldState , state );
119
122
}
120
123
121
124
/**
@@ -127,14 +130,11 @@ public void start() {
127
130
*/
128
131
@ Override
129
132
public void stop () {
130
- synchronized (this ) {
131
- setStateAndNotify (SubscriptionManagerState .STOPPING );
132
- ArrayList <SubscriptionRecord > values = new ArrayList <>(subscriptions .values ());
133
- for (SubscriptionRecord subscription : values ) {
134
- doUnsubscribe (subscription .subscription );
133
+ dispatcher .execute (new Runnable () {
134
+ @ Override public void run () {
135
+ doStop ();
135
136
}
136
- disconnect (true );
137
- }
137
+ });
138
138
}
139
139
140
140
@ Override public SubscriptionManagerState getState () {
@@ -150,67 +150,112 @@ public void stop() {
150
150
}
151
151
152
152
void doSubscribe (Subscription subscription , SubscriptionManager .Callback callback ) {
153
- if (state == SubscriptionManagerState .STOPPING || state == SubscriptionManagerState .STOPPED ) {
153
+ final SubscriptionManagerState oldState ;
154
+ synchronized (this ) {
155
+ oldState = state ;
156
+
157
+ if (state != SubscriptionManagerState .STOPPING && state != SubscriptionManagerState .STOPPED ) {
158
+ timer .cancelTask (INACTIVITY_TIMEOUT_TIMER_TASK_ID );
159
+
160
+ final UUID subscriptionId = UUID .randomUUID ();
161
+
162
+ subscriptions .put (subscriptionId , new SubscriptionRecord (subscriptionId , subscription , callback ));
163
+ if (state == SubscriptionManagerState .DISCONNECTED ) {
164
+ state = SubscriptionManagerState .CONNECTING ;
165
+ transport .connect ();
166
+ } else if (state == SubscriptionManagerState .ACTIVE ) {
167
+ transport .send (new OperationClientMessage .Start (subscriptionId .toString (), subscription , scalarTypeAdapters ));
168
+ }
169
+ }
170
+ }
171
+
172
+ if (oldState == SubscriptionManagerState .STOPPING || oldState == SubscriptionManagerState .STOPPED ) {
154
173
callback .onError (new ApolloSubscriptionException (
155
174
"Illegal state: " + state .name () + " for subscriptions to be created."
156
175
+ " SubscriptionManager.start() must be called to re-enable subscriptions." ));
157
- return ;
176
+ } else if (oldState == SubscriptionManagerState .CONNECTED ) {
177
+ callback .onConnected ();
158
178
}
159
- timer .cancelTask (INACTIVITY_TIMEOUT_TIMER_TASK_ID );
160
179
161
- String subscriptionId = idForSubscription (subscription );
180
+ notifyStateChanged (oldState , state );
181
+ }
182
+
183
+ void doUnsubscribe (Subscription subscription ) {
162
184
synchronized (this ) {
163
- if (subscriptions .containsKey (subscriptionId )) {
164
- callback .onError (new ApolloSubscriptionException ("Already subscribed" ));
165
- return ;
185
+ SubscriptionRecord subscriptionRecord = null ;
186
+ for (SubscriptionRecord record : subscriptions .values ()) {
187
+ if (record .subscription == subscription ) {
188
+ subscriptionRecord = record ;
189
+ }
166
190
}
167
191
168
- subscriptions .put (subscriptionId , new SubscriptionRecord (subscription , callback ));
169
- if (state == SubscriptionManagerState .DISCONNECTED ) {
170
- setStateAndNotify (SubscriptionManagerState .CONNECTING );
171
- transport .connect ();
172
- } else if (state == SubscriptionManagerState .ACTIVE ) {
173
- transport .send (new OperationClientMessage .Start (subscriptionId , subscription , scalarTypeAdapters ));
192
+ if (subscriptionRecord != null ) {
193
+ subscriptions .remove (subscriptionRecord .id );
194
+ if (state == SubscriptionManagerState .ACTIVE || state == SubscriptionManagerState .STOPPING ) {
195
+ transport .send (new OperationClientMessage .Stop (subscriptionRecord .id .toString ()));
196
+ }
197
+ }
198
+
199
+ if (subscriptions .isEmpty () && state != SubscriptionManagerState .STOPPING ) {
200
+ startInactivityTimer ();
174
201
}
175
202
}
176
203
}
177
204
178
- void doUnsubscribe (Subscription subscription ) {
179
- String subscriptionId = idForSubscription (subscription );
180
-
181
- SubscriptionRecord subscriptionRecord ;
205
+ void doStop () {
206
+ final Collection <SubscriptionRecord > subscriptionRecords ;
207
+ final SubscriptionManagerState oldState ;
182
208
synchronized (this ) {
183
- subscriptionRecord = subscriptions .remove (subscriptionId );
184
- if ((subscriptionRecord != null ) && (state == SubscriptionManagerState .ACTIVE || state == SubscriptionManagerState .STOPPING )) {
185
- transport .send (new OperationClientMessage .Stop (subscriptionId ));
186
- }
209
+ oldState = state ;
210
+ state = SubscriptionManagerState .STOPPING ;
187
211
188
- if (subscriptions .isEmpty () && state != SubscriptionManagerState .STOPPING ) {
189
- startInactivityTimer ();
212
+ subscriptionRecords = subscriptions .values ();
213
+
214
+ if (oldState == SubscriptionManagerState .ACTIVE ) {
215
+ for (SubscriptionRecord subscriptionRecord : subscriptionRecords ) {
216
+ transport .send (new OperationClientMessage .Stop (subscriptionRecord .id .toString ()));
217
+ }
190
218
}
219
+
220
+ state = SubscriptionManagerState .STOPPED ;
221
+
222
+ transport .disconnect (new OperationClientMessage .Terminate ());
223
+ subscriptions = new LinkedHashMap <>();
191
224
}
225
+
226
+ for (SubscriptionRecord record : subscriptionRecords ) {
227
+ record .notifyOnCompleted ();
228
+ }
229
+
230
+ notifyStateChanged (oldState , SubscriptionManagerState .STOPPING );
231
+ notifyStateChanged (SubscriptionManagerState .STOPPING , state );
192
232
}
193
233
194
234
void onTransportConnected () {
195
235
final Collection <SubscriptionRecord > subscriptionRecords ;
236
+
237
+ final SubscriptionManagerState oldState ;
196
238
synchronized (this ) {
239
+ oldState = state ;
240
+
197
241
if (state == SubscriptionManagerState .CONNECTING ) {
198
242
subscriptionRecords = subscriptions .values ();
199
- setStateAndNotify ( SubscriptionManagerState .CONNECTED ) ;
243
+ state = SubscriptionManagerState .CONNECTED ;
200
244
transport .send (new OperationClientMessage .Init (connectionParams .provide ()));
201
245
} else {
202
246
subscriptionRecords = Collections .emptyList ();
203
247
}
248
+
249
+ if (state == SubscriptionManagerState .CONNECTED ) {
250
+ timer .schedule (CONNECTION_ACKNOWLEDGE_TIMEOUT_TIMER_TASK_ID , connectionAcknowledgeTimeoutTimerTask , CONNECTION_ACKNOWLEDGE_TIMEOUT );
251
+ }
204
252
}
205
253
206
254
for (SubscriptionRecord record : subscriptionRecords ) {
207
255
record .callback .onConnected ();
208
256
}
209
257
210
- if (state == SubscriptionManagerState .CONNECTED ) {
211
- timer .schedule (CONNECTION_ACKNOWLEDGE_TIMEOUT_TIMER_TASK_ID , connectionAcknowledgeTimeoutTimerTask ,
212
- CONNECTION_ACKNOWLEDGE_TIMEOUT );
213
- }
258
+ notifyStateChanged (oldState , state );
214
259
}
215
260
216
261
void onConnectionAcknowledgeTimeout () {
@@ -234,12 +279,7 @@ public void run() {
234
279
}
235
280
236
281
void onTransportFailure (Throwable t ) {
237
- Collection <SubscriptionRecord > subscriptionRecords ;
238
- synchronized (this ) {
239
- subscriptionRecords = subscriptions .values ();
240
- disconnect (true );
241
- }
242
-
282
+ Collection <SubscriptionRecord > subscriptionRecords = disconnect (true );
243
283
for (SubscriptionRecord record : subscriptionRecords ) {
244
284
record .notifyOnNetworkError (t );
245
285
}
@@ -269,48 +309,62 @@ void onOperationServerMessage(OperationServerMessage message) {
269
309
*
270
310
* @param force if true, always disconnect web socket, regardless of the status of {@link #subscriptions}
271
311
*/
272
- void disconnect (boolean force ) {
312
+ Collection <SubscriptionRecord > disconnect (boolean force ) {
313
+ final SubscriptionManagerState oldState ;
314
+ final Collection <SubscriptionRecord > subscriptionRecords ;
273
315
synchronized (this ) {
316
+ oldState = state ;
317
+ subscriptionRecords = subscriptions .values ();
274
318
if (force || subscriptions .isEmpty ()) {
275
319
transport .disconnect (new OperationClientMessage .Terminate ());
276
- SubscriptionManagerState disconnectionState = (state == SubscriptionManagerState .STOPPING ) ? SubscriptionManagerState .STOPPED
277
- : SubscriptionManagerState .DISCONNECTED ;
278
- setStateAndNotify (disconnectionState );
320
+ state = (state == SubscriptionManagerState .STOPPING ) ? SubscriptionManagerState .STOPPED : SubscriptionManagerState .DISCONNECTED ;
279
321
subscriptions = new LinkedHashMap <>();
280
322
}
281
323
}
324
+
325
+ notifyStateChanged (oldState , state );
326
+
327
+ return subscriptionRecords ;
282
328
}
283
329
284
330
void onConnectionHeartbeatTimeout () {
331
+ final SubscriptionManagerState oldState ;
285
332
synchronized (this ) {
333
+ oldState = state ;
334
+ state = SubscriptionManagerState .DISCONNECTED ;
286
335
transport .disconnect (new OperationClientMessage .Terminate ());
287
- setStateAndNotify (SubscriptionManagerState .DISCONNECTED );
288
-
289
- setStateAndNotify (SubscriptionManagerState .CONNECTING );
336
+ state = SubscriptionManagerState .CONNECTING ;
290
337
transport .connect ();
291
338
}
339
+
340
+ notifyStateChanged (oldState , SubscriptionManagerState .DISCONNECTED );
341
+ notifyStateChanged (SubscriptionManagerState .DISCONNECTED , SubscriptionManagerState .CONNECTING );
292
342
}
293
343
294
344
void onConnectionClosed () {
295
345
Collection <SubscriptionRecord > subscriptionRecords ;
346
+ final SubscriptionManagerState oldState ;
296
347
synchronized (this ) {
348
+ oldState = state ;
349
+
297
350
subscriptionRecords = subscriptions .values ();
298
- setStateAndNotify ( SubscriptionManagerState .DISCONNECTED ) ;
351
+ state = SubscriptionManagerState .DISCONNECTED ;
299
352
subscriptions = new LinkedHashMap <>();
300
353
}
301
354
302
355
for (SubscriptionRecord record : subscriptionRecords ) {
303
356
record .callback .onTerminated ();
304
357
}
358
+
359
+ notifyStateChanged (oldState , state );
305
360
}
306
361
307
362
private void resetConnectionKeepAliveTimerTask () {
308
363
if (connectionHeartbeatTimeoutMs <= 0 ) {
309
364
return ;
310
365
}
311
366
synchronized (this ) {
312
- timer .schedule (CONNECTION_KEEP_ALIVE_TIMEOUT_TIMER_TASK_ID , connectionHeartbeatTimeoutTimerTask ,
313
- connectionHeartbeatTimeoutMs );
367
+ timer .schedule (CONNECTION_KEEP_ALIVE_TIMEOUT_TIMER_TASK_ID , connectionHeartbeatTimeoutTimerTask , connectionHeartbeatTimeoutMs );
314
368
}
315
369
}
316
370
@@ -323,7 +377,11 @@ private void onOperationDataServerMessage(OperationServerMessage.Data message) {
323
377
String subscriptionId = message .id != null ? message .id : "" ;
324
378
SubscriptionRecord subscriptionRecord ;
325
379
synchronized (this ) {
326
- subscriptionRecord = subscriptions .get (subscriptionId );
380
+ try {
381
+ subscriptionRecord = subscriptions .get (UUID .fromString (subscriptionId ));
382
+ } catch (IllegalArgumentException e ) {
383
+ subscriptionRecord = null ;
384
+ }
327
385
}
328
386
329
387
if (subscriptionRecord != null ) {
@@ -347,17 +405,22 @@ private void onOperationDataServerMessage(OperationServerMessage.Data message) {
347
405
}
348
406
349
407
private void onConnectionAcknowledgeServerMessage () {
350
- timer . cancelTask ( CONNECTION_ACKNOWLEDGE_TIMEOUT_TIMER_TASK_ID ) ;
408
+ final SubscriptionManagerState oldState ;
351
409
synchronized (this ) {
410
+ oldState = state ;
411
+
412
+ timer .cancelTask (CONNECTION_ACKNOWLEDGE_TIMEOUT_TIMER_TASK_ID );
413
+
352
414
if (state == SubscriptionManagerState .CONNECTED ) {
353
- setStateAndNotify (SubscriptionManagerState .ACTIVE );
354
- for (Map .Entry <String , SubscriptionRecord > entry : subscriptions .entrySet ()) {
355
- String subscriptionId = entry .getKey ();
356
- Subscription <?, ?, ?> subscription = entry .getValue ().subscription ;
357
- transport .send (new OperationClientMessage .Start (subscriptionId , subscription , scalarTypeAdapters ));
415
+ state = SubscriptionManagerState .ACTIVE ;
416
+ for (SubscriptionRecord subscriptionRecord : subscriptions .values ()) {
417
+ transport .send (new OperationClientMessage .Start (subscriptionRecord .id .toString (), subscriptionRecord .subscription ,
418
+ scalarTypeAdapters ));
358
419
}
359
420
}
360
421
}
422
+
423
+ notifyStateChanged (oldState , state );
361
424
}
362
425
363
426
private void onErrorServerMessage (OperationServerMessage .Error message ) {
@@ -379,31 +442,36 @@ private void onCompleteServerMessage(OperationServerMessage.Complete message) {
379
442
private SubscriptionRecord removeSubscriptionById (String subscriptionId ) {
380
443
SubscriptionRecord subscriptionRecord ;
381
444
synchronized (this ) {
382
- subscriptionRecord = subscriptions .remove (subscriptionId );
445
+ try {
446
+ subscriptionRecord = subscriptions .remove (UUID .fromString (subscriptionId ));
447
+ } catch (IllegalArgumentException e ) {
448
+ subscriptionRecord = null ;
449
+ }
450
+
383
451
if (subscriptions .isEmpty ()) {
384
452
startInactivityTimer ();
385
453
}
386
454
}
387
455
return subscriptionRecord ;
388
456
}
389
457
390
- private void setStateAndNotify (SubscriptionManagerState newState ) {
391
- SubscriptionManagerState oldState = state ;
392
- state = newState ;
458
+ private void notifyStateChanged (SubscriptionManagerState oldState , SubscriptionManagerState newState ) {
459
+ if (oldState == newState ) {
460
+ return ;
461
+ }
462
+
393
463
for (OnSubscriptionManagerStateChangeListener onStateChangeListener : onStateChangeListeners ) {
394
464
onStateChangeListener .onStateChange (oldState , newState );
395
465
}
396
466
}
397
467
398
- static String idForSubscription (Subscription <?, ?, ?> subscription ) {
399
- return subscription .operationId () + "$" + subscription .variables ().valueMap ().hashCode ();
400
- }
401
-
402
468
private static class SubscriptionRecord {
469
+ final UUID id ;
403
470
final Subscription <?, ?, ?> subscription ;
404
471
final SubscriptionManager .Callback <?> callback ;
405
472
406
- SubscriptionRecord (Subscription <?, ?, ?> subscription , SubscriptionManager .Callback <?> callback ) {
473
+ SubscriptionRecord (UUID id , Subscription <?, ?, ?> subscription , SubscriptionManager .Callback <?> callback ) {
474
+ this .id = id ;
407
475
this .subscription = subscription ;
408
476
this .callback = callback ;
409
477
}
@@ -504,7 +572,6 @@ public void run() {
504
572
505
573
timer .schedule (timerTask , delay );
506
574
}
507
-
508
575
}
509
576
510
577
void cancelTask (int taskId ) {
0 commit comments