Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/use remote schemas #123

Merged
merged 3 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@
<argument>https://schemas.dissco.tech/schemas/fdo-type/digital-media/0.3.0/digital-media.json</argument>
<argument>https://schemas.dissco.tech/schemas/fdo-type/machine-annotation-service/0.3.0/machine-annotation-service.json</argument>
<argument>https://schemas.dissco.tech/schemas/fdo-type/annotation/0.3.0/annotation.json</argument>
<argument>https://schemas.dissco.tech/schemas/developer-schema/annotation/0.3.0/annotation-processing-request.json</argument>
</arguments>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import eu.dissco.backend.domain.annotation.batch.AnnotationEvent;
import eu.dissco.backend.domain.annotation.batch.AnnotationEventRequest;
import eu.dissco.backend.exceptions.InvalidAnnotationRequestException;
import eu.dissco.backend.schema.AnnotationRequest;
import eu.dissco.backend.schema.AnnotationProcessingRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
Expand All @@ -14,21 +15,6 @@
@RequiredArgsConstructor
public class SchemaValidatorComponent {

private final JsonSchema annotationSchema;
private final ObjectMapper mapper;

public void validateAnnotationRequest(AnnotationRequest annotationRequest, Boolean isNew)
throws InvalidAnnotationRequestException {
validateId(annotationRequest, isNew);
var annotationRequestNode = mapper.valueToTree(annotationRequest);
var errors = annotationSchema.validate(annotationRequestNode);
if (errors.isEmpty()) {
return;
}
log.error("Invalid annotationRequests request. Errors {}", errors);
throw new InvalidAnnotationRequestException(errors.toString());
}

public void validateAnnotationEventRequest(AnnotationEventRequest event, boolean isNew)
throws InvalidAnnotationRequestException {
if (event.annotationRequests().size() != 1 || event.batchMetadata().size() != 1
Expand All @@ -42,20 +28,21 @@ public void validateAnnotationEventRequest(AnnotationEventRequest event, boolean
throw new InvalidAnnotationRequestException(
"Event can only contain: 1 annotationRequests, 1 batch metadata, and minimum 1 search param");
}
validateAnnotationRequest(event.annotationRequests().get(0), isNew);
validateId(event, isNew);
}


void validateId(AnnotationRequest annotation, Boolean isNew)
private void validateId(AnnotationEventRequest eventRequest, Boolean isNew)
throws InvalidAnnotationRequestException {
if (Boolean.TRUE.equals(isNew) && annotation.getOdsID() != null) {
throw new InvalidAnnotationRequestException(
"Attempting overwrite annotationRequests with \"ods:id\" " + annotation.getOdsID());
for (var annotation : eventRequest.annotationRequests() ){
if (Boolean.TRUE.equals(isNew) && annotation.getOdsID() != null) {
throw new InvalidAnnotationRequestException(
"Attempting overwrite annotationRequests with \"ods:id\" " + annotation.getOdsID());
}
if (Boolean.FALSE.equals(isNew) && annotation.getOdsID() == null) {
throw new InvalidAnnotationRequestException(
"\"ods:id\" not provided for annotationRequests update");
}
}
if (Boolean.FALSE.equals(isNew) && annotation.getOdsID() == null) {
throw new InvalidAnnotationRequestException(
"\"ods:id\" not provided for annotationRequests update");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class SchemaValidatorConfiguration {
public JsonSchema annotationSchema() throws IOException {
var factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
try (InputStream inputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("json-schema/annotation-request.json")) {
.getResourceAsStream("json-schema/annotation-processing-request.json")) {
return factory.getSchema(inputStream);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import eu.dissco.backend.exceptions.NoAnnotationFoundException;
import eu.dissco.backend.exceptions.NotFoundException;
import eu.dissco.backend.properties.ApplicationProperties;
import eu.dissco.backend.schema.AnnotationRequest;
import eu.dissco.backend.schema.AnnotationProcessingRequest;
import eu.dissco.backend.service.AnnotationService;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
Expand Down Expand Up @@ -103,7 +103,6 @@ public ResponseEntity<JsonApiWrapper> createAnnotation(Authentication authentica
@RequestBody JsonApiRequestWrapper requestBody, HttpServletRequest request)
throws JsonProcessingException, ForbiddenException, InvalidAnnotationRequestException {
var annotation = getAnnotationFromRequest(requestBody);
schemaValidator.validateAnnotationRequest(annotation, true);
var userId = authentication.getName();
log.info("Received new annotationRequests from user: {}", userId);
var annotationResponse = service.persistAnnotation(annotation, userId, getPath(request));
Expand Down Expand Up @@ -200,13 +199,13 @@ public ResponseEntity<Void> deleteAnnotation(Authentication authentication,
}
}

private AnnotationRequest getAnnotationFromRequest(JsonApiRequestWrapper requestBody)
private AnnotationProcessingRequest getAnnotationFromRequest(JsonApiRequestWrapper requestBody)
throws JsonProcessingException {
if (!requestBody.data().type().equals(ANNOTATION_TYPE)) {
throw new IllegalArgumentException(
"Invalid type. Type must be " + ANNOTATION_TYPE + " but was " + requestBody.data().type());
}
return mapper.treeToValue(requestBody.data().attributes(), AnnotationRequest.class);
return mapper.treeToValue(requestBody.data().attributes(), AnnotationProcessingRequest.class);
}

private AnnotationEventRequest getAnnotationFromRequestEvent(JsonApiRequestWrapper requestBody)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package eu.dissco.backend.domain.annotation.batch;

import eu.dissco.backend.schema.AnnotationRequest;
import eu.dissco.backend.schema.AnnotationProcessingRequest;
import java.util.List;

public record AnnotationEventRequest(List<AnnotationRequest> annotationRequests,
public record AnnotationEventRequest(List<AnnotationProcessingRequest> annotationRequests,
List<BatchMetadata> batchMetadata) {

}
59 changes: 14 additions & 45 deletions src/main/java/eu/dissco/backend/service/AnnotationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@
import eu.dissco.backend.schema.Agent.Type;
import eu.dissco.backend.schema.Annotation;
import eu.dissco.backend.schema.Annotation.OaMotivation;
import eu.dissco.backend.schema.AnnotationRequest;
import eu.dissco.backend.schema.OaHasBody;
import eu.dissco.backend.schema.OaHasBody__1;
import eu.dissco.backend.schema.OaHasSelector__1;
import eu.dissco.backend.schema.OaHasTarget;
import eu.dissco.backend.schema.OaHasTarget__1;
import eu.dissco.backend.schema.AnnotationProcessingRequest;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
Expand Down Expand Up @@ -61,15 +56,6 @@ public class AnnotationService {
private final UserService userService;
private final ObjectMapper mapper;

private static OaHasSelector__1 buildSelector(OaHasTarget oaHasTarget) {
var selector = new OaHasSelector__1();
for (var entry : oaHasTarget.getOaHasSelector()
.getAdditionalProperties().entrySet()) {
selector.setAdditionalProperty(entry.getKey(), entry.getValue());
}
return selector;
}

public JsonApiWrapper getAnnotation(String id, String path) {
var annotation = repository.getAnnotation(id);
var dataNode = new JsonApiData(id, ANNOTATION, mapper.valueToTree(annotation));
Expand Down Expand Up @@ -97,10 +83,10 @@ public JsonApiListResponseWrapper getAnnotations(int pageNumber, int pageSize,
return wrapListResponse(annotationsPlusOne, pageNumber, pageSize, path);
}

public JsonApiWrapper persistAnnotation(AnnotationRequest annotationRequest, String userId,
public JsonApiWrapper persistAnnotation(AnnotationProcessingRequest annotationProcessingRequest, String userId,
String path) throws ForbiddenException, JsonProcessingException {
var user = getUserInformation(userId);
var annotation = buildAnnotation(annotationRequest, user, false);
var annotation = buildAnnotation(annotationProcessingRequest, user, false);
var response = annotationClient.postAnnotation(annotation);
return formatResponse(response, path);
}
Expand Down Expand Up @@ -166,55 +152,38 @@ private User getUserInformation(String userId) throws ForbiddenException {
return user;
}

private Annotation buildAnnotation(AnnotationRequest annotationRequest, User user,
private Annotation buildAnnotation(AnnotationProcessingRequest annotationProcessingRequest, User user,
boolean isUpdate) {
var annotation = new Annotation()
.withOaMotivation(OaMotivation.fromValue(annotationRequest.getOaMotivation().value()))
.withOaMotivatedBy(annotationRequest.getOaMotivatedBy())
.withOaHasBody(buildBody(annotationRequest.getOaHasBody()))
.withOaHasTarget(buildTarget(annotationRequest.getOaHasTarget()))
.withOaMotivation(OaMotivation.fromValue(annotationProcessingRequest.getOaMotivation().value()))
.withOaMotivatedBy(annotationProcessingRequest.getOaMotivatedBy())
.withOaHasBody(annotationProcessingRequest.getOaHasBody())
.withOaHasTarget(annotationProcessingRequest.getOaHasTarget())
.withDctermsCreated(Date.from(Instant.now()))
.withDctermsCreator(new Agent().withType(Type.SCHEMA_PERSON).withId(user.orcid())
.withSchemaName(user.lastName()));
if (isUpdate) {
annotation.setId(annotationRequest.getOdsID());
annotation.setOdsID(annotationRequest.getOdsID());
annotation.setId(annotationProcessingRequest.getOdsID());
annotation.setOdsID(annotationProcessingRequest.getOdsID());
}
return annotation;
}

private OaHasTarget__1 buildTarget(OaHasTarget oaHasTarget) {
return new OaHasTarget__1()
.withId(oaHasTarget.getId())
.withOdsID(oaHasTarget.getOdsID())
.withType(oaHasTarget.getType())
.withOdsType(oaHasTarget.getOdsType())
.withOaHasSelector(buildSelector(oaHasTarget));
}

private OaHasBody__1 buildBody(OaHasBody annotationRequestBody) {
return new OaHasBody__1()
.withType(annotationRequestBody.getType())
.withOaValue(annotationRequestBody.getOaValue())
.withDctermsReferences(annotationRequestBody.getDctermsReferences())
.withOdsScore(annotationRequestBody.getOdsScore());
}

private Annotation parseToAnnotation(JsonNode response) throws JsonProcessingException {
return mapper.treeToValue(response, Annotation.class);
}

public JsonApiWrapper updateAnnotation(String id, AnnotationRequest annotationRequest,
public JsonApiWrapper updateAnnotation(String id, AnnotationProcessingRequest annotationProcessingRequest,
String userId,
String path, String prefix, String suffix)
throws NoAnnotationFoundException, ForbiddenException, JsonProcessingException {
var user = getUserInformation(userId);
var result = repository.getAnnotationForUser(id, user.orcid());
if (result > 0) {
if (annotationRequest.getOdsID() == null) {
annotationRequest.setOdsID(id);
if (annotationProcessingRequest.getOdsID() == null) {
annotationProcessingRequest.setOdsID(id);
}
var annotation = buildAnnotation(annotationRequest, user, true);
var annotation = buildAnnotation(annotationProcessingRequest, user, true);
var response = annotationClient.updateAnnotation(prefix, suffix, annotation);
return formatResponse(response, path);
} else {
Expand Down
85 changes: 85 additions & 0 deletions src/main/resources/json-schema/annotation-processing-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"$id": "https://schemas.dissco.tech/schemas/fdo-type/annotation/0.3.0/annotation-processing-request.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$comment": "Annotation Version 0.3.0",
"title": "Annotation",
"description": "Annotation sent to the processing service from DiSSCover or a MAS",
"type": "object",
"properties": {
"@id": {
"type": "string",
"description": "The unique identifier (handle) of the Annotation object",
"pattern": "^https:\/\/hdl\\.handle\\.net\/[\\w.]+\/(.){3}-(.){3}-(.){3}",
"examples": [
"https://hdl.handle.net/20.5000.1025/XXX-XXX-XXX"
]
},
"@type": {
"type": "string",
"description": "The type of the object, in this case ods:Annotation",
"const": "ods:Annotation"
},
"ods:ID": {
"type": "string",
"description": "The handle of the annotation. It is a unique identifier for the annotation. It is composed of the handle of the document followed by a slash and a unique identifier for the annotation.",
"pattern": "^https:\/\/hdl\\.handle\\.net\/[\\w.]+\/(.){3}-(.){3}-(.){3}",
"examples": [
"https://hdl.handle.net/20.5000.1025/XXX-XXX-XXX"
]
},
"oa:motivation": {
"description": "The motivation for the annotation. Based on a selection of https://www.w3.org/TR/annotation-model/#motivation-and-purpose. The motivation ods:adding is added for DiSSCo's purposes.",
"enum": [
"ods:adding",
"ods:deleting",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be tombstoning no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is automatically pulled from schemas.dissco,tech, it's ods:deleted there

"oa:assessing",
"oa:editing",
"oa:commenting"
]
},
"oa:motivatedBy": {
"type": "string",
"description": "Describes the reason for the annotation. https://www.w3.org/TR/annotation-vocab/#motivatedby",
"examples": [
"The country is incorrect"
]
},
"oa:hasTarget": {
"type": "object",
"description": "Indicates the particular object and part of the object on which the annotation has been made.",
"$ref": "https://schemas.dissco.tech/schemas/fdo-type/annotation/0.3.0/annotation-target.json"
},
"oa:hasBody": {
"type": "object",
"description": "The body of the annotation contains the specific value of the annotation",
"$ref": "https://schemas.dissco.tech/schemas/fdo-type/annotation/0.3.0/annotation-body.json"
},
"dcterms:creator": {
"type": "object",
"description": "Contains an ods:Agent object",
"$ref": "https://schemas.dissco.tech/schemas/fdo-type/shared-model/0.3.0/agent.json"
},
"dcterms:created": {
"type": "string",
"format": "date-time",
"description": "The date and time when the annotation was created. https://purl.org/dc/terms/created"
},
"ods:placeInBatch": {
"type": "integer",
"description": "The place of the annotation in the batch"
},
"ods:batchID": {
"type": "string",
"format": "uuid",
"description": "Internally generated PID to identify the batch the annotation was generated by"
}
},
"required": [
"oa:motivation",
"oa:hasTarget",
"oa:hasBody",
"dcterms:creator",
"dcterms:created"
],
"additionalProperties": false
}
Loading
Loading