use of com.amplifyframework.datastore.appsync.ModelWithMetadata 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.ModelWithMetadata in project amplify-android by aws-amplify.
the class SyncProcessor method hydrateSchemaIfNeeded.
// Cast to T
@SuppressWarnings("unchecked")
private <T extends Model> ModelWithMetadata<T> hydrateSchemaIfNeeded(ModelWithMetadata<T> original, ModelSchema schema) {
if (original.getModel() instanceof SerializedModel) {
SerializedModel originalModel = (SerializedModel) original.getModel();
SerializedModel newModel = SerializedModel.builder().serializedData(SerializedModel.parseSerializedData(originalModel.getSerializedData(), schema.getName(), schemaRegistry)).modelSchema(schema).build();
return new ModelWithMetadata<>((T) newModel, original.getSyncMetadata());
} else {
return original;
}
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata 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.");
});
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata in project amplify-android by aws-amplify.
the class MutationProcessor method modelWithSchemaAdded.
private <T extends Model> ModelWithMetadata<? extends Model> modelWithSchemaAdded(ModelWithMetadata<T> modelWithMetadata, ModelSchema modelSchema) {
final SerializedModel originalModel = (SerializedModel) modelWithMetadata.getModel();
final SerializedModel newModel = SerializedModel.builder().serializedData(SerializedModel.parseSerializedData(originalModel.getSerializedData(), modelSchema.getName(), schemaRegistry)).modelSchema(modelSchema).build();
return new ModelWithMetadata<>(newModel, modelWithMetadata.getSyncMetadata());
}
use of com.amplifyframework.datastore.appsync.ModelWithMetadata 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);
});
}
Aggregations