use of com.amplifyframework.datastore.appsync.AppSync in project amplify-android by aws-amplify.
the class SubscriptionProcessorTest method arrangeSubscription.
private static void arrangeSubscription(AppSync appSync, Answer<Cancelable> answer, ModelSchema modelSchema, SubscriptionType subscriptionType) throws DataStoreException {
AppSync stub = doAnswer(answer).when(appSync);
SubscriptionProcessor.SubscriptionMethod method = SubscriptionProcessor.subscriptionMethodFor(stub, subscriptionType);
method.subscribe(eq(modelSchema), anyConsumer(), anyConsumer(), anyConsumer(), anyAction());
}
use of com.amplifyframework.datastore.appsync.AppSync in project amplify-android by aws-amplify.
the class SubscriptionProcessorTest method arrangeDataEmittingSubscription.
@SuppressWarnings("SameParameterValue")
private static <T extends Model> void arrangeDataEmittingSubscription(AppSync appSync, ModelSchema modelSchema, SubscriptionType subscriptionType, GraphQLResponse<ModelWithMetadata<T>> response) throws DataStoreException {
Answer<Cancelable> answer = invocation -> {
final int startConsumerIndex = 1;
Consumer<String> onStart = invocation.getArgument(startConsumerIndex);
onStart.accept(RandomString.string());
final int dataConsumerIndex = 2;
Consumer<GraphQLResponse<ModelWithMetadata<T>>> onData = invocation.getArgument(dataConsumerIndex);
onData.accept(response);
return new NoOpCancelable();
};
arrangeSubscription(appSync, answer, modelSchema, subscriptionType);
}
use of com.amplifyframework.datastore.appsync.AppSync in project amplify-android by aws-amplify.
the class SyncProcessorTest method syncAndExpect.
private void syncAndExpect(int numPages, int maxSyncRecords) throws AmplifyException, InterruptedException {
initSyncProcessor(maxSyncRecords);
// Arrange a subscription to the storage adapter. We're going to watch for changes.
// We expect to see content here as a result of the SyncProcessor applying updates.
final TestObserver<StorageItemChange<? extends Model>> adapterObserver = storageAdapter.observe().test();
// Arrange: return some responses for the sync() call on the RemoteModelState
AppSyncMocking.SyncConfigurator configurator = AppSyncMocking.sync(appSync);
List<ModelWithMetadata<BlogOwner>> expectedResponseItems = new ArrayList<>();
String token = null;
for (int pageIndex = 0; pageIndex < numPages; pageIndex++) {
String nextToken = pageIndex < numPages - 1 ? RandomString.string() : null;
ModelWithMetadata<BlogOwner> randomBlogOwner = randomBlogOwnerWithMetadata();
configurator.mockSuccessResponse(BlogOwner.class, token, nextToken, randomBlogOwner);
if (expectedResponseItems.size() < maxSyncRecords) {
expectedResponseItems.add(randomBlogOwner);
}
token = nextToken;
}
// Act: Call hydrate, and await its completion - assert it completed without error
TestObserver<ModelWithMetadata<? extends Model>> hydrationObserver = TestObserver.create();
syncProcessor.hydrate().subscribe(hydrationObserver);
// Wait 2 seconds, or 1 second per 100 pages, whichever is greater
long timeoutMs = Math.max(OP_TIMEOUT_MS, TimeUnit.SECONDS.toMillis(numPages / 100));
assertTrue(hydrationObserver.await(timeoutMs, TimeUnit.MILLISECONDS));
hydrationObserver.assertNoErrors();
hydrationObserver.assertComplete();
// Since hydrate() completed, the storage adapter observer should see some values.
// There should be a total of four changes on storage adapter
// A model and a metadata save for each of the two BlogOwner-type items
// Additionally, there should be 4 last sync time records, one for each of the
// models managed by the system.
adapterObserver.awaitCount(expectedResponseItems.size() * 2 + 4);
// Validate the changes emitted from the storage adapter's observe().
assertEquals(// Expect items as described above.
Observable.fromIterable(expectedResponseItems).flatMap(modelWithMutation -> Observable.fromArray(modelWithMutation.getModel(), modelWithMutation.getSyncMetadata())).toSortedList(SortByModelId::compare).blockingGet(), // Actually...
Observable.fromIterable(adapterObserver.values()).map(StorageItemChange::item).filter(item -> !LastSyncMetadata.class.isAssignableFrom(item.getClass())).toSortedList(SortByModelId::compare).blockingGet());
// Lastly: validate the current contents of the storage adapter.
// There should be 2 BlogOwners, and 2 MetaData records.
List<? extends Model> itemsInStorage = storageAdapter.query(modelProvider);
assertEquals(itemsInStorage.toString(), expectedResponseItems.size() * 2 + modelProvider.models().size(), itemsInStorage.size());
assertEquals(// Expect the 4 items for the bloggers (2 models and their metadata)
Observable.fromIterable(expectedResponseItems).flatMap(blogger -> Observable.fromArray(blogger.getModel(), blogger.getSyncMetadata())).toList().map(HashSet::new).blockingGet(), Observable.fromIterable(storageAdapter.query(modelProvider)).filter(item -> !LastSyncMetadata.class.isAssignableFrom(item.getClass())).toList().map(HashSet::new).blockingGet());
adapterObserver.dispose();
hydrationObserver.dispose();
}
use of com.amplifyframework.datastore.appsync.AppSync in project amplify-android by aws-amplify.
the class SubscriptionProcessor method subscriptionObservable.
private <T extends Model> Observable<SubscriptionEvent<? extends Model>> subscriptionObservable(AppSync appSync, SubscriptionType subscriptionType, AbortableCountDownLatch<DataStoreException> latch, ModelSchema modelSchema) {
return Observable.<GraphQLResponse<ModelWithMetadata<T>>>create(emitter -> {
SubscriptionMethod method = subscriptionMethodFor(appSync, subscriptionType);
AtomicReference<String> subscriptionId = new AtomicReference<>();
Cancelable cancelable = method.subscribe(modelSchema, token -> {
LOG.debug("Subscription started for " + subscriptionType.name() + " " + modelSchema.getName() + " subscriptionId: " + token);
subscriptionId.set(token);
latch.countDown();
}, emitter::onNext, dataStoreException -> {
if (isExceptionType(dataStoreException, AppSyncErrorType.UNAUTHORIZED)) {
// Ignore Unauthorized errors, so that DataStore can still be used even if the user is only
// authorized to read a subset of the models.
latch.countDown();
LOG.warn("Unauthorized failure:" + subscriptionType.name() + " " + modelSchema.getName());
} else if (isExceptionType(dataStoreException, AppSyncErrorType.OPERATION_DISABLED)) {
// Ignore OperationDisabled errors, so that DataStore can be used even without subscriptions.
// This logic is only in place to address a specific use case, and should not be used without
// unless you have consulted with AWS. It is subject to be deprecated/removed in the future.
latch.countDown();
LOG.warn("Operation disabled:" + subscriptionType.name() + " " + modelSchema.getName());
} else {
if (latch.getCount() > 0) {
// An error occurred during startup. Abort and notify the Orchestrator by throwing the
// exception from startSubscriptions.
latch.abort(dataStoreException);
} else {
// An error occurred after startup. Notify the Orchestrator via the onFailure action.
onFailure.accept(dataStoreException);
}
}
}, () -> {
LOG.debug("Subscription completed:" + subscriptionId.get());
emitter.onComplete();
});
// When the observable is disposed, we need to call cancel() on the subscription
// so it can properly dispose of resources if necessary. For the AWS API plugin,
// this means closing the underlying network connection.
emitter.setDisposable(AmplifyDisposables.fromCancelable(cancelable));
}).doOnError(subscriptionError -> LOG.warn("An error occurred on the remote " + subscriptionType.name() + " subscription for model " + modelSchema.getName(), subscriptionError)).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).map(SubscriptionProcessor::unwrapResponse).filter(modelWithMetadata -> {
QueryPredicate predicate = queryPredicateProvider.getPredicate(modelSchema.getName());
return predicate.evaluate(modelWithMetadata.getModel());
}).map(modelWithMetadata -> SubscriptionEvent.<T>builder().type(fromSubscriptionType(subscriptionType)).modelWithMetadata(modelWithMetadata).modelSchema(modelSchema).build());
}
use of com.amplifyframework.datastore.appsync.AppSync in project amplify-android by aws-amplify.
the class SubscriptionProcessor method startSubscriptions.
/**
* Start subscribing to model mutations.
*/
synchronized void startSubscriptions() throws DataStoreException {
int subscriptionCount = modelProvider.modelNames().size() * SubscriptionType.values().length;
// Create a latch with the number of subscriptions are requesting. Each of these will be
// counted down when each subscription's onStarted event is called.
AbortableCountDownLatch<DataStoreException> latch = new AbortableCountDownLatch<>(subscriptionCount);
// Need to create a new buffer so we can properly handle retries and stop/start scenarios.
// Re-using the same buffer has some unexpected results due to the replay aspect of the subject.
buffer = ReplaySubject.create();
Set<Observable<SubscriptionEvent<? extends Model>>> subscriptions = new HashSet<>();
for (ModelSchema modelSchema : modelProvider.modelSchemas().values()) {
for (SubscriptionType subscriptionType : SubscriptionType.values()) {
subscriptions.add(subscriptionObservable(appSync, subscriptionType, latch, modelSchema));
}
}
ongoingOperationsDisposable.add(Observable.merge(subscriptions).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).doOnSubscribe(disposable -> LOG.info("Starting processing subscription events.")).doOnError(failure -> LOG.warn("Reading subscription events has failed.", failure)).doOnComplete(() -> LOG.warn("Reading subscription events is completed.")).subscribe(buffer::onNext, buffer::onError, buffer::onComplete));
boolean subscriptionsStarted;
try {
LOG.debug("Waiting for subscriptions to start.");
subscriptionsStarted = latch.abortableAwait(adjustedTimeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException exception) {
LOG.warn("Subscription operations were interrupted during setup.");
return;
}
if (subscriptionsStarted) {
Amplify.Hub.publish(HubChannel.DATASTORE, HubEvent.create(DataStoreChannelEventName.SUBSCRIPTIONS_ESTABLISHED));
LOG.info(String.format(Locale.US, "Started subscription processor for models: %s of types %s.", modelProvider.modelNames(), Arrays.toString(SubscriptionType.values())));
} else {
throw new DataStoreException("Timed out waiting for subscription processor to start.", "Retry");
}
}
Aggregations