annorepo

AnnoRepoClient

A Java/Kotlin client for connecting to an AnnoRepo server and wrapping the communication with the endpoints.

Installation

Maven

Add the following to your pom.xml

<dependency>
    <groupId>io.github.knaw-huc</groupId>
    <artifactId>annorepo-client</artifactId>
    <version>0.7.0</version>
</dependency>

Initialization

Kotlin:

val client = AnnoRepoClient(
    serverURI = URI.create("http://localhost:8080"),
    apiKey = apiKey,
    userAgent = "name to identity this client in the User-Agent header"
)

apiKey and userAgent are optional, so in Java there are three constructors:

Java

AnnoRepoClient1 client = new AnnoRepoClient(URI.create("http://localhost:8080"));
AnnoRepoClient1 client2 = new AnnoRepoClient(URI.create("http://localhost:8080", apiKey));
AnnoRepoClient1 client3 = new AnnoRepoClient(URI.create("http://localhost:8080", apiKey, userAgent));

The client will try to connect to the AnnoRepo server at the given URI, and throw a RuntimeException if this is not possible. After connecting, you will be able to check the version of annorepo that the server is running:

Kotlin:

val serverVersion = client.serverVersion

Java

String serverVersion = client.getServerVersion();

as well as whether this server requires authentication:

Kotlin:

val serverNeedsAuthentication = client.serverNeedsAuthentication

Java

Boolean serverNeedsAuthentication = client.getServerNeedsAuthentication();

General information about the endpoint calls

The calls to the annorepo server endpoints will return an Either of a RequestError (in case of an unexpected server response) and an endpoint-specific result (in case of a successful response).

The Either has several methods to handle the left or right hand side; for example: fold, where you provide functions to deal with the left and right sides:

Kotlin:

client.getAbout().fold(
    { error -> println(error) },
    { result -> println(result) }
)

Java

Boolean success = client.getAbout().fold(
    error -> {
        System.out.println(error.toString());
        return false;
    },
    result -> {
        System.out.println(result.toString());
        return true;
    }
)

Get information about the server

Kotlin:

val result = client.getAbout()

Java

Either<RequestError, GetAboutResult> aboutResult = client.getAbout();

Annotation containers

Creating a container

Parameters:

If the annorepo instance has authorization enabled, the user creating a container will automatically get Role.ADMIN rights to that container. (see Container User administration)

Kotlin:

val preferredName = "my-container"
val label = "A container for all my annotations"
val readOnlyForAnonymousUsers = true
val success = client.createContainer(preferredName, label, readOnlyForAnonymousUsers).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, location, containerName, eTag): CreateContainerResult ->
        doSomethingWith(containerName, location, eTag)
        true
    }
)

Java

String preferredName = "my-container";
String label = "A container for all my annotations";
Boolean readOnlyForAnonymousUsers = true;
Boolean success = client.createContainer(preferredName,label,readOnlyForAnonymousUsers).fold(
    (RequestError error) -> {
        handleError(error);
        return false;
    },
    (ARResult.CreateContainerResult result) -> {
        String containerName = result.getContainerName();
        URI location = result.getLocation();
        String eTag = result.getETag();
        doSomethingWith(containerName,location,eTag);
        return true;
    }
);

On a succeeding call, the CreateContainerResult contains:

Retrieving a container

Kotlin:

val either = client.createContainer()
    .map { (_, _, containerName): CreateContainerResult ->
        client.getContainer(containerName)
            .map { (response, entity, eTag1): GetContainerResult ->
                val entityTag = response.entityTag
                doSomethingWith(eTag1, entity, entityTag)
                true
            }
        true
    }

Java

String containerName = "my-container";
client.getContainer(containerName).map(
        (ARResult.GetContainerResult result) -> {
            String eTag = result.getETag();
            String entity = result.getEntity();
            EntityTag entityTag = result.getResponse().getEntityTag();
            doSomethingWith(eTag, entity, entityTag);
            return true;
        }
);

Deleting a container

Kotlin:

client.deleteContainer(containerName, eTag)
    .map { result: DeleteContainerResult -> true }

Java

client.deleteContainer(containerName, eTag).map(
        (ARResult.DeleteContainerResult result) -> true
);

Changing the read-only for anonynous users setting for a container

Kotlin:

client.setAnonymousUserReadAccess(containerName, true)
    .map { result: SetAnonymousUserReadAccessResult -> true }

Java

client.setAnonymousUserReadAccess(containerName, true).map(
        (ARResult.SetAnonymousUserReadAccessResult result) -> true
);

Annotations

Adding an annotation to a container

Kotlin:

val containerName = "my-container"
val annotation = WebAnnotation.Builder()
    .withBody("http://example.org/annotation1")
    .withTarget("http://example.org/target")
    .build()
client.createAnnotation(containerName, annotation).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, location, _, annotationName, eTag): CreateAnnotationResult ->
        doSomethingWith(annotationName, location, eTag)
        true
    }
)

Java

String containerName = "my-container";
WebAnnotation annotation = new WebAnnotation.Builder()
        .withBody("http://example.org/annotation1")
        .withTarget("http://example.org/target")
        .build();
client.createAnnotation(containerName, annotation).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.CreateAnnotationResult result) -> {
            URI location = result.getLocation();
            String eTag = result.getETag();
            String annotationName = result.getAnnotationName();
            doSomethingWith(annotationName, location, eTag);
            return true;
        }
);

Retrieving an annotation

Kotlin:

val containerName = "my-container"
val annotationName = "my-annotation"
client.getAnnotation(containerName, annotationName).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, eTag, annotation): GetAnnotationResult ->
        doSomethingWith(annotation, eTag)
        true
    }
)

Java

String containerName = "my-container";
String annotationName = "my-annotation";
client.getAnnotation(containerName, annotationName).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.GetAnnotationResult result) -> {
            String eTag = result.getETag();
            Map<String, Object> annotation = result.getAnnotation();
            doSomethingWith(annotation, eTag);
            return true;
        }
);

Updating an annotation

Kotlin:

val containerName = "my-container"
val annotationName = "my-annotation"
val eTag = "abcde"
val updatedAnnotation = WebAnnotation.Builder()
    .withBody("http://example.org/annotation2")
    .withTarget("http://example.org/target")
    .build()
client.updateAnnotation(containerName, annotationName, eTag, updatedAnnotation)
    .fold(
        { error: RequestError ->
            handleError(error)
            false
        },
        { (_, location, _, _, newETag): CreateAnnotationResult ->
            doSomethingWith(annotationName, location, newETag)
            true
        }
)

Java

String containerName = "my-container";
String annotationName = "my-annotation";
String eTag = "abcdefg";
WebAnnotation updatedAnnotation = new WebAnnotation.Builder()
        .withBody("http://example.org/annotation2")
        .withTarget("http://example.org/target")
        .build();
client.updateAnnotation(containerName, annotationName, eTag, updatedAnnotation).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.CreateAnnotationResult result) -> {
            URI location = result.getLocation();
            String newETag = result.getETag();
            doSomethingWith(annotationName, location, newETag);
            return true;
        }
);

Deleting an annotation

Kotlin:

val containerName = "my-container"
val annotationName = "my-annotation"
val eTag = "abcdefg"
val success = client.deleteAnnotation(containerName, annotationName, eTag).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { _: DeleteAnnotationResult -> true }
)

Java

String containerName = "my-container";
String annotationName = "my-annotation";
String eTag = "abcdefg";
Boolean success = client.deleteAnnotation(containerName, annotationName, eTag).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.DeleteAnnotationResult result) -> true
);

Batch uploading of annotations

Kotlin:

val containerName = "my-container"
val annotation1 = WebAnnotation.Builder()
    .withBody("http://example.org/annotation1")
    .withTarget("http://example.org/target1")
    .build()
val annotation2 = WebAnnotation.Builder()
    .withBody("http://example.org/annotation2")
    .withTarget("http://example.org/target2")
    .build()
val annotations = java.util.List.of(annotation1, annotation2)
val success = client.batchUpload(containerName, annotations).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, annotationIdentifiers): BatchUploadResult ->
        doSomethingWith(annotationIdentifiers)
        true
    }
)

Java

String containerName = "my-container";
WebAnnotation annotation1 = new WebAnnotation.Builder()
        .withBody("http://example.org/annotation1")
        .withTarget("http://example.org/target1")
        .build();
WebAnnotation annotation2 = new WebAnnotation.Builder()
        .withBody("http://example.org/annotation2")
        .withTarget("http://example.org/target2")
        .build();

List<WebAnnotation> annotations = List.of(annotation1, annotation2);
Boolean success = client.batchUpload(containerName, annotations).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.BatchUploadResult result) -> {
            List<AnnotationIdentifier> annotationIdentifiers = result.getAnnotationData();
            doSomethingWith(annotationIdentifiers);
            return true;
        }
);

Querying a container

Kotlin:

Construct the query as a (nested) map. When successful, the call returns a createSearchResult, which contains the queryId to be used in getting the result pages.

val query = mapOf("body" to "urn:example:body42")
val createSearchResult = this.createSearch(containerName = containerName, query = query)

Java

String containerName = "volume-1728";
Map<String, Object> query = Map.of("body.type", "Page");
Boolean success = client.createSearch(containerName, query).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.CreateSearchResult result) -> {
            URI location = result.getLocation();
            String queryId = result.getQueryId();
            doSomethingWith(location, queryId);
            return true;
        }
);

Retrieving a result page

Kotlin:

val resultPageResult = this.getSearchResultPage(
    containerName = containerName,
    queryId = queryId,
    page = 0
)

Java

client.getSearchResultPage(containerName, queryId, 0).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        result -> {
            AnnotationPage annotationPage = result.getAnnotationPage();
            doSomethingWith(annotationPage);
            return true;
        }
);

Filtering Container Annotations

This function combines creating the search and iterating over the search result pages and extracting the annotations from those pages into a stream. Since there could be unexpected response from the server, the stream returned is one of Either<RequestError, FilterContainerAnnotationsResult>

Kotlin:

val query = mapOf("body.type" to "Page")
val filterContainerAnnotationsResult: FilterContainerAnnotationsResult? =
    this.filterContainerAnnotations(containerName, query2).orNull()
filterContainerAnnotationsResult?.let {
    it.annotations.forEach { item ->
        item.fold(
            { error: RequestError -> handleError(error) },
            { annotation: Map<String, Any> -> handleSuccess(annotation) }
        )
    }
}

Java

Map<String, ?> query = Map.of("body.type", "Resolution");
client.filterContainerAnnotations("my-container", query).fold(
        error -> {
            System.out.println(error.toString());
            return false;
        },
        result -> {
            result.getAnnotations().limit(5).forEach(item -> {
                System.out.println(item.orNull());
                System.out.println();
            });
            return true;
        }
);

Retrieving search information

Kotlin:

val getSearchInfoResult = this.getSearchInfo(
    containerName = containerName,
    queryId = queryId
)

Java

Boolean success = client.getSearchInfo(containerName, queryId).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        result -> {
            SearchInfo searchInfo = result.getSearchInfo();
            doSomethingWith(searchInfo);
            return true;
        }
);

Querying all accessible containers

Kotlin:

val query = mapOf("body.type" to "Page")
val success = client.createGlobalSearch(query).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, location, queryId): CreateSearchResult ->
        doSomethingWith(location, queryId)
        true
    }
)

Java

Map<String, Object> query = Map.of("body.type", "Page");
Boolean success = client.createGlobalSearch(query).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.CreateSearchResult result) -> {
            URI location = result.getLocation();
            String queryId = result.getQueryId();
            doSomethingWith(location, queryId);
            return true;
        }
);

Get the global search status

Kotlin:

val query = mapOf("body.type" to "Page")
val optionalQueryId = client.createGlobalSearch(query = query).fold(
    { error: RequestError ->
        handleError(error)
        null
    },
    { (_, _, queryId): CreateSearchResult -> queryId }
)
optionalQueryId?.apply {
    val success = client.getGlobalSearchStatus(queryId = this).fold(
        { error: RequestError ->
            handleError(error)
            false
        },
        { (_, searchStatus): GetGlobalSearchStatusResult ->
            doSomethingWith(searchStatus)
            true
        }
    )
}

Java

Map<String, Object> query = Map.of("body.type", "Page");
Optional<String> optionalQueryId = client.createGlobalSearch(query).fold(
        (RequestError error) -> {
            handleError(error);
            return Optional.empty();
        },
        (ARResult.CreateSearchResult result) -> Optional.of(result.getQueryId())
);
optionalQueryId.ifPresent(queryId -> {
    Boolean success = client.getGlobalSearchStatus(queryId).fold(
            (RequestError error) -> {
                handleError(error);
                return false;
            },
            result -> {
                SearchStatusSummary searchStatus = result.getSearchStatus();
                doSomethingWith(searchStatus);
                return true;
            }
    );
    assertThat(success).isTrue();
});

Get the global search results

Kotlin:

val query = mapOf("type" to "Annotation")
val optionalQueryId = client.createGlobalSearch(query = query).fold(
    { error: RequestError ->
        handleError(error)
        null
    },
    { (_, _, queryId): CreateSearchResult -> queryId }
)
optionalQueryId?.apply {
    client.getGlobalSearchResultPage(queryId = this, page = 0, retryUntilDone = true).fold(
        { error: RequestError ->
            handleError(error)
        },
        { (_, annotationPage): GetSearchResultPageResult ->
            doSomethingWith(annotationPage)
        }
    )
}

Java

Map<String, Object> query = Map.of("type", "Annotation");
Optional<String> optionalQueryId = client.createGlobalSearch(query).fold(
        (RequestError error) -> {
            handleError(error);
            return Optional.empty();
        },
        (ARResult.CreateSearchResult result) -> Optional.of(result.getQueryId())
);
assertThat(optionalQueryId).isPresent();
optionalQueryId.ifPresent(queryId -> client.getGlobalSearchResultPage(queryId, 0, true).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (GetSearchResultPageResult result) -> {
            AnnotationPage annotationPage = result.getAnnotationPage();
            doSomethingWith(annotationPage);
            return true;
        }
));

Indexes

Adding an index to a container

Kotlin:

val containerName = "volume-1728"
val fieldName = "body.type"
val indexType = IndexType.HASHED
val success = client.addIndex(containerName, fieldName, indexType).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { result: AddIndexResult -> true }
)

Java

String containerName = "volume-1728";
String fieldName = "body.type";
IndexType indexType = IndexType.HASHED;
Boolean success = client.addIndex(containerName, fieldName, indexType).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        result -> true
);

Retrieving index information

Kotlin:

val containerName = "volume-1728"
val fieldName = "body.type"
val indexType = IndexType.HASHED
val success = client.getIndex(containerName, fieldName, indexType).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { (_, indexConfig): GetIndexResult ->
        doSomethingWith(indexConfig)
        true
    }
)

Java

String containerName = "volume-1728";
String fieldName = "body.type";
IndexType indexType = IndexType.HASHED;
Boolean success = client.getIndex(containerName, fieldName, indexType).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.GetIndexResult result) -> {
            IndexConfig indexConfig = result.getIndexConfig();
            doSomethingWith(indexConfig);
            return true;
        }
);

Listing all indexes for a container

Kotlin:

val containerName = "volume-1728"
val success = client.listIndexes(containerName).fold(
    { error: RequestError ->
        handleError(error)
        false
    }, { (_, indexes): ListIndexesResult ->
        doSomethingWith(indexes)
        true
    }
)

Java

String containerName = "volume-1728";
Boolean success = client.listIndexes(containerName).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.ListIndexesResult result) -> {
            List<IndexConfig> indexes = result.getIndexes();
            doSomethingWith(indexes);
            return true;
        }
);

Deleting an index

Kotlin:

val containerName = "volume-1728"
val fieldName = "body.type"
val indexType = IndexType.HASHED
val success = client.deleteIndex(containerName, fieldName, indexType).fold(
    { error: RequestError ->
        handleError(error)
        false
    },
    { result: DeleteIndexResult -> true }
)

Java

String containerName = "volume-1728";
String fieldName = "body.type";
IndexType indexType = IndexType.HASHED;
Boolean success = client.deleteIndex(containerName, fieldName, indexType).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.DeleteIndexResult result) -> true
);

Retrieving information about the fields used in container annotations

Kotlin:

val containerName = "volume-1728"
client.getFieldInfo(containerName).fold(
    { error: RequestError -> handleError(error) },
    { (_, fieldInfo): AnnotationFieldInfoResult -> doSomethingWith(fieldInfo) }
)

Java

String containerName = "volume-1728";
client.getFieldInfo(containerName).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        result -> {
            Map<String, Integer> fieldInfo = result.getFieldInfo();
            doSomethingWith(fieldInfo);
            return true;
        }
);

Retrieving the distinct field values used in container annotations

Kotlin:

val containerName = "volume-1728"
client.getDistinctFieldValues(containerName, "body.type").fold(
        { error: RequestError -> handleError(error) },
        { (_, distinctValues): ARResult.DistinctAnnotationFieldValuesResult ->
            doSomethingWith(distinctValues)
        })

Java

String containerName = "volume-1728";
String fieldName = "body.type";
Boolean success = client.getDistinctFieldValues(containerName, fieldName).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        result -> {
            List<Object> distinctValues = result.getDistinctValues();
            doSomethingWith(distinctValues);
            return true;
        }
);

User administration

These admin functionalities are only available on annorepo servers that have authentication enabled. The root api-key is required for these calls.

Adding users

Kotlin:

val userEntries = listOf(UserEntry(userName, apiKey))
client.addUsers(userEntries).fold(
    { error -> println(error.message) },
    { result ->
        val accepted = result.accepted
        val rejected = result.rejected
        doSomething(accepted, rejected)
    }
)

Java

List<UserEntry> userEntries = List.of(new UserEntry("userName", "apiKey"));
client.addUsers(userEntries).fold(
        error -> {
            System.out.println(error.getMessage());
            return false;
        },
        result -> {
            List<String> accepted = result.getAccepted();
            List<RejectedUserEntry> rejected = result.getRejected();
            doSomething(accepted, rejected);
            return true;
        }
);

Retrieving users

Kotlin:

client.getUsers().fold(
    { error: RequestError -> println(error) },
    { result: ARResult.UsersResult ->
        result.userEntries.forEach { ue: UserEntry ->
            val userName: String = ue.userName
            val apiKey: String = ue.apiKey
            doSomething(userName, apiKey)
        }
    }
)

Java

client.getUsers().fold(
        error -> {
            System.out.println(error.getMessage());
            return false;
        },
        result -> {
            List<UserEntry> userEntries = result.getUserEntries();
            for (UserEntry ue : userEntries) {
                String userName = ue.getUserName();
                String apiKey = ue.getApiKey();
                doSomething(userName, apiKey);
            }
            return true;
        }
);

Deleting a user

Kotlin:

val deletionSucceeded = client.deleteUser(userName).isRight()

Java

Boolean deletionSucceeded = client.deleteUser(userName).isRight();

Container User administration

These endpoints are only functional on annorepo servers that have authentication enabled.

Only users with admin rights to the given container (and the root user) can use these endpoints.

Users that have been previously added via the .addUsers() endpoint can be added to the given container, with one of these roles:

Adding container users

Kotlin:

val containerName = "my-container"
val containerUserEntries = listOf(
    ContainerUserEntry("user1", Role.EDITOR),
    ContainerUserEntry("user2", Role.GUEST),
    ContainerUserEntry("admin2", Role.ADMIN),
)
client.addContainerUsers(containerName, containerUserEntries).fold(
    { error: RequestError -> handleError(error) },
    { (_, newContainerUsersList): ContainerUsersResult -> doSomethingWith(newContainerUsersList) }
)

Java

String containerName = "my-container";
List<ContainerUserEntry> containerUserEntries = List.of(
        new ContainerUserEntry("user1", Role.EDITOR),
        new ContainerUserEntry("user2", Role.GUEST),
        new ContainerUserEntry("admin2", Role.ADMIN)
);
Boolean success = client.addContainerUsers(containerName, containerUserEntries).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.ContainerUsersResult result) -> {
            List<ContainerUserEntry> newContainerUsersList = result.getContainerUserEntries();
            doSomethingWith(newContainerUsersList);
            return true;
        }
);

Reading the current list of container users

Kotlin:

val containerName = "my-container"
client.getContainerUsers(containerName).fold(
    { error: RequestError -> handleError(error) },
    { (_, containerUserEntries): ContainerUsersResult -> doSomethingWith(containerUserEntries) }
)

Java

String containerName = "my-container";
Boolean success = client.getContainerUsers(containerName).fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.ContainerUsersResult result) -> {
            List<ContainerUserEntry> containerUserEntries = result.getContainerUserEntries();
            doSomethingWith(containerUserEntries);
            return true;
        }
);

Deleting a container user

Kotlin:

val containerName = "my-container"
val userName = "userName"
val deletionSuccess = client.deleteContainerUser(containerName, userName).isRight()
assertThat(deletionSuccess).isTrue

Java

String containerName = "my-container";
String userName = "userName";
boolean deletionSuccess = client.deleteContainerUser(containerName, userName).isRight();

Listing all containers accessible to the user

Kotlin:

client.getMyContainers().fold(
    { error: RequestError -> handleError(error) },
    { (_, containers): ARResult.MyContainersResult -> doSomethingWith(containers) }
)

Java

Boolean success = client.getMyContainers().fold(
        (RequestError error) -> {
            handleError(error);
            return false;
        },
        (ARResult.MyContainersResult result) -> {
            Map<String, List<String>> containerMap = result.getContainers();
            doSomethingWith(containerMap);
            return true;
        }
);