A Java/Kotlin client for connecting to an AnnoRepo server and wrapping the communication with the endpoints.
Add the following to your pom.xml
<dependency>
<groupId>io.github.knaw-huc</groupId>
<artifactId>annorepo-client</artifactId>
<version>0.7.0</version>
</dependency>
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();
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;
}
)
Kotlin:
val result = client.getAbout()
Java
Either<RequestError, GetAboutResult> aboutResult = client.getAbout();
Parameters:
preferredName
: optional String, indicating the preferred name for the container. May be overridden by the server.label
: optional String, a human-readable label for the container.readOnlyForAnonymousUsers
: optional Boolean, set to true
to make the container read-only for anonymous users (default: false).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:
response
: the raw javax.ws.rs.core.Responselocation
: the contents of the location
headercontainerName
: the name of the container.eTag
: the eTag for the containerKotlin:
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;
}
);
Kotlin:
client.deleteContainer(containerName, eTag)
.map { result: DeleteContainerResult -> true }
Java
client.deleteContainer(containerName, eTag).map(
(ARResult.DeleteContainerResult result) -> true
);
read-only for anonynous users
setting for a containerKotlin:
client.setAnonymousUserReadAccess(containerName, true)
.map { result: SetAnonymousUserReadAccessResult -> true }
Java
client.setAnonymousUserReadAccess(containerName, true).map(
(ARResult.SetAnonymousUserReadAccessResult result) -> true
);
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;
}
);
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;
}
);
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;
}
);
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
);
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;
}
);
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;
}
);
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;
}
);
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;
}
);
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;
}
);
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;
}
);
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();
});
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;
}
));
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
);
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;
}
);
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;
}
);
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
);
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;
}
);
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;
}
);
These admin functionalities are only available on annorepo servers that have authentication enabled. The root api-key is required for these calls.
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;
}
);
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;
}
);
Kotlin:
val deletionSucceeded = client.deleteUser(userName).isRight()
Java
Boolean deletionSucceeded = client.deleteUser(userName).isRight();
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:
Role.GUEST
-> user has read-only access to the containerRole.EDITOR
-> user can read and write to the container, but cannot add or delete container usersRole.ADMIN
-> user can read and write to the container, and add or delete users for this containerKotlin:
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;
}
);
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;
}
);
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();
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;
}
);