use of com.amplifyframework.core.model.Model 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);
});
}
use of com.amplifyframework.core.model.Model in project amplify-android by aws-amplify.
the class SQLiteModelTree method descendantsOf.
/**
* Returns a map of descendants of a set of models (of same type).
* A model is a child of its parent if it uses its parent's ID as foreign key.
* @param root Collection of models to query its descendants of.
* @return List of models that are descendants of given models. These models will
* have the correct model type and ID, but no other field will be populated.
*/
<T extends Model> List<Model> descendantsOf(Collection<T> root) {
if (Empty.check(root)) {
return new ArrayList<>();
}
Map<ModelSchema, Set<String>> modelMap = new LinkedHashMap<>();
Model rootModel = root.iterator().next();
ModelSchema rootSchema = registry.getModelSchemaForModelClass(getModelName(rootModel));
Set<String> rootIds = new HashSet<>();
for (T model : root) {
rootIds.add(model.getId());
}
recurseTree(modelMap, rootSchema, rootIds);
List<Model> descendants = new ArrayList<>();
for (Map.Entry<ModelSchema, Set<String>> entry : modelMap.entrySet()) {
ModelSchema schema = entry.getKey();
for (String id : entry.getValue()) {
if (rootModel.getClass() == SerializedModel.class) {
SerializedModel dummyItem = SerializedModel.builder().serializedData(Collections.singletonMap("id", id)).modelSchema(schema).build();
descendants.add(dummyItem);
} else {
// Create dummy model instance using just the ID and model type
String dummyJson = gson.toJson(Collections.singletonMap("id", id));
Model dummyItem = gson.fromJson(dummyJson, schema.getModelClass());
descendants.add(dummyItem);
}
}
}
return descendants;
}
use of com.amplifyframework.core.model.Model in project amplify-android by aws-amplify.
the class SQLiteStorageAdapter method initialize.
/**
* {@inheritDoc}
*/
@Override
public synchronized void initialize(@NonNull Context context, @NonNull Consumer<List<ModelSchema>> onSuccess, @NonNull Consumer<DataStoreException> onError, @NonNull DataStoreConfiguration dataStoreConfiguration) {
Objects.requireNonNull(context);
Objects.requireNonNull(onSuccess);
Objects.requireNonNull(onError);
// Create a thread pool large enough to take advantage of parallelization, but small enough to avoid
// OutOfMemoryError and CursorWindowAllocationException issues.
this.threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * THREAD_POOL_SIZE_MULTIPLIER);
this.context = context;
this.dataStoreConfiguration = dataStoreConfiguration;
threadPool.submit(() -> {
try {
/*
* Start with a fresh registry.
*/
schemaRegistry.clear();
/*
* Create {@link ModelSchema} objects for the corresponding {@link Model}.
* Any exception raised during this when inspecting the Model classes
* through reflection will be notified via the `onError` callback.
*/
schemaRegistry.register(modelsProvider.modelSchemas(), modelsProvider.customTypeSchemas());
/*
* Create the CREATE TABLE and CREATE INDEX commands for each of the
* Models. Instantiate {@link SQLiteStorageHelper} to execute those
* create commands.
*/
this.sqlCommandFactory = new SQLiteCommandFactory(schemaRegistry, gson);
CreateSqlCommands createSqlCommands = getCreateCommands(modelsProvider.modelNames());
sqliteStorageHelper = SQLiteStorageHelper.getInstance(context, databaseName, DATABASE_VERSION, createSqlCommands);
/*
* Create and/or open a database. This also invokes
* {@link SQLiteStorageHelper#onCreate(SQLiteDatabase)} which executes the tasks
* to create tables and indexes. When the function returns without any exception
* being thrown, invoke the `onError` callback.
*
* Errors are thrown when there is no write permission to the database, no space
* left in the database for any write operation and other errors thrown while
* creating and opening a database. All errors are passed through the
* `onError` callback.
*
* databaseConnectionHandle represents a connection handle to the database.
* All database operations will happen through this handle.
*/
databaseConnectionHandle = sqliteStorageHelper.getWritableDatabase();
/*
* Create helper instance that can traverse through model relations.
*/
this.sqliteModelTree = new SQLiteModelTree(schemaRegistry, databaseConnectionHandle);
/*
* Create a command processor which runs the actual SQL transactions.
*/
this.sqlCommandProcessor = new SQLCommandProcessor(databaseConnectionHandle);
sqlQueryProcessor = new SqlQueryProcessor(sqlCommandProcessor, sqlCommandFactory, schemaRegistry);
syncStatus = new SyncStatus(sqlQueryProcessor, dataStoreConfiguration);
/*
* Detect if the version of the models stored in SQLite is different
* from the version passed in through {@link ModelProvider#version()}.
* Delete the database if there is a version change.
*/
toBeDisposed.add(updateModels().subscribe(() -> onSuccess.accept(Immutable.of(new ArrayList<>(schemaRegistry.getModelSchemaMap().values()))), throwable -> onError.accept(new DataStoreException("Error in initializing the SQLiteStorageAdapter", throwable, AmplifyException.TODO_RECOVERY_SUGGESTION))));
} catch (Exception exception) {
onError.accept(new DataStoreException("Error in initializing the SQLiteStorageAdapter", exception, "See attached exception"));
}
});
}
use of com.amplifyframework.core.model.Model in project amplify-android by aws-amplify.
the class RxApiBindingTest method subscribeStartsAndGetsCancelled.
/**
* Verify that the subscription starts and is cancelled gracefully.
* @throws InterruptedException Not expected.
*/
@SuppressWarnings("rawtypes")
@Test
public void subscribeStartsAndGetsCancelled() throws InterruptedException {
// Arrange a category behavior which emits an expected sequence of callback events
String token = RandomString.string();
GraphQLRequest<Model> request = createMockSubscriptionRequest(Model.class);
ConnectionStateEvent expectedConnectionStateEvent = new ConnectionStateEvent(ConnectionState.CONNECTED, token);
doAnswer(invocation -> {
final int onStartPosition = 1;
final int onCompletePosition = 4;
Consumer<String> onStart = invocation.getArgument(onStartPosition);
Action onComplete = invocation.getArgument(onCompletePosition);
onStart.accept(token);
GraphQLOperation mockApiOperation = mock(GraphQLOperation.class);
doAnswer(apiCancelInvocation -> {
onComplete.call();
return null;
}).when(mockApiOperation).cancel();
return mockApiOperation;
}).when(delegate).subscribe(eq(request), anyConsumer(), anyConsumer(), anyConsumer(), anyAction());
// Act: subscribe via binding
RxSubscriptionOperation<GraphQLResponse<Model>> rxOperation = rxApi.subscribe(request);
// Act: subscribe via binding
TestObserver<GraphQLResponse<Model>> dataObserver = rxOperation.observeSubscriptionData().test();
TestObserver<ConnectionStateEvent> startObserver = rxOperation.observeConnectionState().test();
startObserver.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
startObserver.assertValue(expectedConnectionStateEvent);
startObserver.assertNoErrors();
// Act: cancel the subscription
Completable.timer(1, TimeUnit.SECONDS).andThen(Completable.fromAction(rxOperation::cancel)).subscribe();
dataObserver.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
dataObserver.assertNoValues();
dataObserver.assertNoErrors();
dataObserver.assertComplete();
startObserver.assertComplete();
}
use of com.amplifyframework.core.model.Model in project amplify-android by aws-amplify.
the class RxApiBindingTest method queryEmitsFailure.
/**
* When the API behavior emits a failure for a query, so too should the Rx binding.
* @throws InterruptedException If interrupted while test observer is awaiting terminal event
*/
@Test
public void queryEmitsFailure() throws InterruptedException {
// Arrange: category behavior emits a failure
ApiException expectedFailure = new ApiException("Expected", "Failure");
GraphQLRequest<Iterable<Model>> listRequest = createMockListRequest(Model.class);
doAnswer(invocation -> {
// 0 = clazz, 1 = onResponse, 2 = onFailure
final int positionOfOnFailure = 2;
Consumer<ApiException> onFailure = invocation.getArgument(positionOfOnFailure);
onFailure.accept(expectedFailure);
return null;
}).when(delegate).query(eq(listRequest), anyConsumer(), anyConsumer());
// Act: access query() method via Rx binding
TestObserver<GraphQLResponse<Iterable<Model>>> observer = rxApi.query(listRequest).test();
// Assert: failure bubbles up to Rx
observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
observer.assertError(expectedFailure);
verify(delegate).query(eq(listRequest), anyConsumer(), anyConsumer());
}
Aggregations