Search in sources :

Example 26 with ModelMetadata

use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.

the class AWSDataStorePluginTest method stopStopsSyncUntilNextInteraction.

/**
 * Verify that when the stop method is called, the following happens
 * - All remote synchronization processes are stopped
 * - On the next interaction with the DataStore, the synchronization processes are restarted.
 * @throws JSONException on failure to arrange plugin config
 * @throws AmplifyException on failure to arrange API plugin via Amplify facade
 */
@Test
public void stopStopsSyncUntilNextInteraction() throws AmplifyException, JSONException {
    ApiCategory mockApiCategory = mockApiCategoryWithGraphQlApi();
    ApiPlugin<?> mockApiPlugin = mockApiCategory.getPlugin(MOCK_API_PLUGIN_NAME);
    JSONObject dataStorePluginJson = new JSONObject().put("syncIntervalInMinutes", 60);
    AWSDataStorePlugin awsDataStorePlugin = AWSDataStorePlugin.builder().modelProvider(modelProvider).apiCategory(mockApiCategory).build();
    SynchronousDataStore synchronousDataStore = SynchronousDataStore.delegatingTo(awsDataStorePlugin);
    awsDataStorePlugin.configure(dataStorePluginJson, context);
    awsDataStorePlugin.initialize(context);
    // Trick the DataStore since it's not getting initialized as part of the Amplify.initialize call chain
    Amplify.Hub.publish(HubChannel.DATASTORE, HubEvent.create(InitializationStatus.SUCCEEDED));
    // Setup objects
    Person person1 = createPerson("Test", "Dummy I");
    Person person2 = createPerson("Test", "Dummy II");
    // Mock responses for person 1
    doAnswer(invocation -> {
        int indexOfResponseConsumer = 1;
        Consumer<GraphQLResponse<ModelWithMetadata<Person>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
        ModelMetadata modelMetadata = new ModelMetadata(person1.getId(), false, 1, Temporal.Timestamp.now());
        ModelWithMetadata<Person> modelWithMetadata = new ModelWithMetadata<>(person1, modelMetadata);
        onResponse.accept(new GraphQLResponse<>(modelWithMetadata, Collections.emptyList()));
        return mock(GraphQLOperation.class);
    }).when(mockApiPlugin).mutate(any(), any(), any());
    HubAccumulator apiInteractionObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.OUTBOX_MUTATION_PROCESSED, 1).start();
    // Save person 1
    synchronousDataStore.save(person1);
    Person result1 = synchronousDataStore.get(Person.class, person1.getId());
    assertEquals(person1, result1);
    apiInteractionObserver.await();
    verify(mockApiCategory).mutate(argThat(getMatcherFor(person1)), any(), any());
    // Mock responses for person 2
    doAnswer(invocation -> {
        int indexOfResponseConsumer = 1;
        Consumer<GraphQLResponse<ModelWithMetadata<Person>>> onResponse = invocation.getArgument(indexOfResponseConsumer);
        ModelMetadata modelMetadata = new ModelMetadata(person2.getId(), false, 1, Temporal.Timestamp.now());
        ModelWithMetadata<Person> modelWithMetadata = new ModelWithMetadata<>(person2, modelMetadata);
        onResponse.accept(new GraphQLResponse<>(modelWithMetadata, Collections.emptyList()));
        return mock(GraphQLOperation.class);
    }).when(mockApiPlugin).mutate(any(), any(), any());
    // Do the thing!
    synchronousDataStore.stop();
    assertRemoteSubscriptionsCancelled();
    apiInteractionObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.OUTBOX_MUTATION_PROCESSED, 1).start();
    HubAccumulator orchestratorInitObserver = HubAccumulator.create(HubChannel.DATASTORE, DataStoreChannelEventName.READY, 1).start();
    // Interact with the DataStore after the stop
    synchronousDataStore.save(person2);
    // Verify person 2 was published to the cloud
    apiInteractionObserver.await();
    // Verify the orchestrator started back up and subscriptions are active.
    orchestratorInitObserver.await();
    assertRemoteSubscriptionsStarted();
    // Verify person 1 and 2 are in the DataStore
    List<Person> results = synchronousDataStore.list(Person.class);
    assertEquals(Arrays.asList(person1, person2), results);
    verify(mockApiCategory, atLeastOnce()).mutate(argThat(getMatcherFor(person2)), any(), any());
}
Also used : ModelWithMetadata(com.amplifyframework.datastore.appsync.ModelWithMetadata) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) JSONObject(org.json.JSONObject) SynchronousDataStore(com.amplifyframework.testutils.sync.SynchronousDataStore) ApiCategory(com.amplifyframework.api.ApiCategory) HubAccumulator(com.amplifyframework.testutils.HubAccumulator) Person(com.amplifyframework.testmodels.personcar.Person) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) Test(org.junit.Test)

Example 27 with ModelMetadata

use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.

the class Merger method merge.

/**
 * Merge an item back into the local store, using a default strategy.
 * TODO: Change this method to return a Maybe, and remove the Consumer argument.
 * @param modelWithMetadata A model, combined with metadata about it
 * @param changeTypeConsumer A callback invoked when the merge method saves or deletes the model.
 * @param <T> Type of model
 * @return A completable operation to merge the model
 */
<T extends Model> Completable merge(ModelWithMetadata<T> modelWithMetadata, Consumer<StorageItemChange.Type> changeTypeConsumer) {
    AtomicReference<Long> startTime = new AtomicReference<>();
    return Completable.defer(() -> {
        ModelMetadata metadata = modelWithMetadata.getSyncMetadata();
        boolean isDelete = Boolean.TRUE.equals(metadata.isDeleted());
        int incomingVersion = metadata.getVersion() == null ? -1 : metadata.getVersion();
        T model = modelWithMetadata.getModel();
        return versionRepository.findModelVersion(model).onErrorReturnItem(-1).filter(currentVersion -> currentVersion == -1 || incomingVersion > currentVersion).flatMapCompletable(shouldMerge -> {
            Completable firstStep;
            if (mutationOutbox.hasPendingMutation(model.getId())) {
                LOG.info("Mutation outbox has pending mutation for " + model.getId() + ". Saving the metadata, but not model itself.");
                firstStep = Completable.complete();
            } else {
                firstStep = (isDelete ? delete(model, changeTypeConsumer) : save(model, changeTypeConsumer));
            }
            return firstStep.andThen(save(metadata, NoOpConsumer.create()));
        }).doOnComplete(() -> {
            announceSuccessfulMerge(modelWithMetadata);
            LOG.debug("Remote model update was sync'd down into local storage: " + modelWithMetadata);
        }).onErrorComplete(failure -> {
            if (!ErrorInspector.contains(failure, SQLiteConstraintException.class)) {
                return false;
            }
            LOG.warn("Sync failed: foreign key constraint violation: " + modelWithMetadata, failure);
            return true;
        }).doOnError(failure -> LOG.warn("Failed to sync remote model into local storage: " + modelWithMetadata, failure));
    }).doOnSubscribe(disposable -> startTime.set(System.currentTimeMillis())).doOnTerminate(() -> {
        long duration = System.currentTimeMillis() - startTime.get();
        LOG.verbose("Merged a single item in " + duration + " ms.");
    });
}
Also used : Amplify(com.amplifyframework.core.Amplify) HubChannel(com.amplifyframework.hub.HubChannel) NonNull(androidx.annotation.NonNull) QueryPredicates(com.amplifyframework.core.model.query.predicate.QueryPredicates) ModelWithMetadata(com.amplifyframework.datastore.appsync.ModelWithMetadata) StorageItemChange(com.amplifyframework.datastore.storage.StorageItemChange) DataStoreChannelEventName(com.amplifyframework.datastore.DataStoreChannelEventName) Model(com.amplifyframework.core.model.Model) Completable(io.reactivex.rxjava3.core.Completable) AtomicReference(java.util.concurrent.atomic.AtomicReference) ErrorInspector(com.amplifyframework.datastore.utils.ErrorInspector) Logger(com.amplifyframework.logging.Logger) Objects(java.util.Objects) Consumer(com.amplifyframework.core.Consumer) LocalStorageAdapter(com.amplifyframework.datastore.storage.LocalStorageAdapter) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) SQLiteConstraintException(android.database.sqlite.SQLiteConstraintException) NoOpConsumer(com.amplifyframework.core.NoOpConsumer) HubEvent(com.amplifyframework.hub.HubEvent) Completable(io.reactivex.rxjava3.core.Completable) AtomicReference(java.util.concurrent.atomic.AtomicReference) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata)

Example 28 with ModelMetadata

use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.

the class VersionRepositoryTest method emitsErrorWhenMetadataHasNullVersion.

/**
 * When you try to get the version for a model, and there is metadata for the model
 * in the DataStore, BUT the version info is not populated, this should return an
 * {@link DataStoreException}.
 * @throws DataStoreException
 *         NOT EXPECTED. This happens on failure to arrange data before test action.
 *         The expected DataStoreException is communicated via callback, not thrown
 *         on the calling thread. It's a different thing than this.
 * @throws InterruptedException If interrupted while awaiting terminal result in test observer
 */
@Test
public void emitsErrorWhenMetadataHasNullVersion() throws DataStoreException, InterruptedException {
    // Arrange a model an metadata into the store, but the metadtaa doesn't contain a valid version
    BlogOwner blogOwner = BlogOwner.builder().name("Jameson").build();
    ModelMetadata metadata = new ModelMetadata(blogOwner.getModelName() + "|" + blogOwner.getId(), null, null, null);
    storageAdapter.save(blogOwner, metadata);
    // Act: try to get the version.
    TestObserver<Integer> observer = versionRepository.findModelVersion(blogOwner).test();
    assertTrue(observer.await(REASONABLE_WAIT_TIME, TimeUnit.MILLISECONDS));
    // Assert: the single emitted a DataStoreException.
    observer.assertError(error -> {
        if (!(error instanceof DataStoreException)) {
            return false;
        }
        String expectedMessage = String.format(Locale.US, "Metadata for item with id = %s had null version.", blogOwner.getId());
        return expectedMessage.equals(error.getMessage());
    });
}
Also used : DataStoreException(com.amplifyframework.datastore.DataStoreException) BlogOwner(com.amplifyframework.testmodels.commentsblog.BlogOwner) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) Test(org.junit.Test)

Example 29 with ModelMetadata

use of com.amplifyframework.datastore.appsync.ModelMetadata in project amplify-android by aws-amplify.

the class ConflictResolver method resolve.

@NonNull
<T extends Model> Single<ModelWithMetadata<T>> resolve(@NonNull PendingMutation<T> pendingMutation, @NonNull AppSyncConflictUnhandledError<T> conflictUnhandledError) {
    final DataStoreConflictHandler conflictHandler;
    try {
        conflictHandler = configurationProvider.getConfiguration().getConflictHandler();
    } catch (DataStoreException badConfigurationProvider) {
        return Single.error(badConfigurationProvider);
    }
    ModelWithMetadata<T> serverData = conflictUnhandledError.getServerVersion();
    ModelMetadata metadata = serverData.getSyncMetadata();
    T local = getMutatedModelFromSerializedModel(pendingMutation);
    T remote = getServerModel(serverData, pendingMutation.getMutatedItem());
    ConflictData<T> conflictData = ConflictData.create(local, remote);
    return Single.<ConflictResolutionDecision<? extends Model>>create(emitter -> conflictHandler.onConflictDetected(conflictData, emitter::onSuccess)).flatMap(decision -> {
        @SuppressWarnings("unchecked") ConflictResolutionDecision<T> typedDecision = (ConflictResolutionDecision<T>) decision;
        return resolveModelAndMetadata(conflictData, metadata, typedDecision);
    });
}
Also used : DataStoreConfigurationProvider(com.amplifyframework.datastore.DataStoreConfigurationProvider) Single(io.reactivex.rxjava3.core.Single) DataStoreConflictHandler(com.amplifyframework.datastore.DataStoreConflictHandler) SerializedModel(com.amplifyframework.core.model.SerializedModel) NonNull(androidx.annotation.NonNull) ModelWithMetadata(com.amplifyframework.datastore.appsync.ModelWithMetadata) Model(com.amplifyframework.core.model.Model) AppSync(com.amplifyframework.datastore.appsync.AppSync) AppSyncConflictUnhandledError(com.amplifyframework.datastore.appsync.AppSyncConflictUnhandledError) SchemaRegistry(com.amplifyframework.core.model.SchemaRegistry) ConflictData(com.amplifyframework.datastore.DataStoreConflictHandler.ConflictData) GsonFactory(com.amplifyframework.util.GsonFactory) Objects(java.util.Objects) DataStoreException(com.amplifyframework.datastore.DataStoreException) Type(java.lang.reflect.Type) Gson(com.google.gson.Gson) ModelSchema(com.amplifyframework.core.model.ModelSchema) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) GraphQLResponse(com.amplifyframework.api.graphql.GraphQLResponse) ConflictResolutionDecision(com.amplifyframework.datastore.DataStoreConflictHandler.ConflictResolutionDecision) DataStoreException(com.amplifyframework.datastore.DataStoreException) ConflictResolutionDecision(com.amplifyframework.datastore.DataStoreConflictHandler.ConflictResolutionDecision) DataStoreConflictHandler(com.amplifyframework.datastore.DataStoreConflictHandler) ModelMetadata(com.amplifyframework.datastore.appsync.ModelMetadata) NonNull(androidx.annotation.NonNull)

Aggregations

ModelMetadata (com.amplifyframework.datastore.appsync.ModelMetadata)29 ModelWithMetadata (com.amplifyframework.datastore.appsync.ModelWithMetadata)23 Test (org.junit.Test)23 BlogOwner (com.amplifyframework.testmodels.commentsblog.BlogOwner)22 Temporal (com.amplifyframework.core.model.temporal.Temporal)7 GraphQLResponse (com.amplifyframework.api.graphql.GraphQLResponse)6 ModelSchema (com.amplifyframework.core.model.ModelSchema)6 HubAccumulator (com.amplifyframework.testutils.HubAccumulator)4 NonNull (androidx.annotation.NonNull)3 ApiCategory (com.amplifyframework.api.ApiCategory)3 SerializedModel (com.amplifyframework.core.model.SerializedModel)3 Person (com.amplifyframework.testmodels.personcar.Person)3 RandomString (com.amplifyframework.testutils.random.RandomString)3 SynchronousDataStore (com.amplifyframework.testutils.sync.SynchronousDataStore)3 HashMap (java.util.HashMap)3 CountDownLatch (java.util.concurrent.CountDownLatch)3 GraphQLLocation (com.amplifyframework.api.graphql.GraphQLLocation)2 GraphQLPathSegment (com.amplifyframework.api.graphql.GraphQLPathSegment)2 Amplify (com.amplifyframework.core.Amplify)2 Consumer (com.amplifyframework.core.Consumer)2