From 6fc507f056b64ae6e5ff3f3671eb818213e51aba Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Thu, 7 Nov 2024 10:21:51 +0100 Subject: [PATCH] partial refactoring --- README.md | 4 + .../service/DirectIndexService.java | 7 ++ .../app/directindex/sword/SwordException.java | 23 ++++ .../sword/SwordFileSetUrlController.java | 34 ------ .../sword/SwordFileUrlController.java | 43 ------- .../sword/SwordMetadataUrlController.java | 69 ----------- .../sword/SwordObjectUrlController.java | 62 ---------- .../sword/SwordServiceUrlController.java | 113 +++++++++++++++--- .../sword/SwordTemporaryUrlController.java | 58 --------- .../directindex/sword/model/SwordError.java | 24 ++++ .../app/directindex/sword/CommonTest.java | 16 +++ 11 files changed, 171 insertions(+), 282 deletions(-) create mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordException.java delete mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordFileSetUrlController.java delete mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordFileUrlController.java delete mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordMetadataUrlController.java delete mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordObjectUrlController.java delete mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/SwordTemporaryUrlController.java create mode 100644 src/main/java/eu/dnetlib/app/directindex/sword/model/SwordError.java create mode 100644 src/test/java/eu/dnetlib/app/directindex/sword/CommonTest.java diff --git a/README.md b/README.md index b9c901f..a328507 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # dnet-directindex-application +### SWORD Deposit Protocol v.3 +* *[Main site](https://sword.cottagelabs.com/swordv3/)* +* *[Protocol Specification](https://swordapp.github.io/swordv3/swordv3.html)* +* *[Protocol Behaviours](https://swordapp.github.io/swordv3/swordv3-behaviours.html)* diff --git a/src/main/java/eu/dnetlib/app/directindex/service/DirectIndexService.java b/src/main/java/eu/dnetlib/app/directindex/service/DirectIndexService.java index 24cc8ec..0602df1 100644 --- a/src/main/java/eu/dnetlib/app/directindex/service/DirectIndexService.java +++ b/src/main/java/eu/dnetlib/app/directindex/service/DirectIndexService.java @@ -2,6 +2,8 @@ package eu.dnetlib.app.directindex.service; import org.springframework.stereotype.Service; +import eu.dnetlib.app.directindex.sword.model.SwordMetadataDocument; + @Service public class DirectIndexService { @@ -15,4 +17,9 @@ public class DirectIndexService { } + public void prepareMetadataInsertion(final SwordMetadataDocument body) { + // TODO Auto-generated method stub + + } + } diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordException.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordException.java new file mode 100644 index 0000000..5a89415 --- /dev/null +++ b/src/main/java/eu/dnetlib/app/directindex/sword/SwordException.java @@ -0,0 +1,23 @@ +package eu.dnetlib.app.directindex.sword; + +public class SwordException extends Exception { + + private static final long serialVersionUID = -6401402258941503950L; + + private final int httpCode; + private final String httpMessage; + + public SwordException(final int httpCode, final String httpMessage) { + this.httpCode = httpCode; + this.httpMessage = httpMessage; + } + + public int getHttpCode() { + return httpCode; + } + + public String getHttpMessage() { + return httpMessage; + } + +} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileSetUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileSetUrlController.java deleted file mode 100644 index 6e6372d..0000000 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileSetUrlController.java +++ /dev/null @@ -1,34 +0,0 @@ -package eu.dnetlib.app.directindex.sword; - -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController("/api/directindex/file") -public class SwordFileSetUrlController { - - @PutMapping("/") - public @ResponseBody Object replaceFileSet( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Content-Type") final String contentType, - @RequestHeader("Digest") final String digest, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @RequestHeader(value = "Packaging", defaultValue = "http://purl.org/net/sword/3.0/package/Binary") final String packaging) { - // TODO - return null; - } - - @DeleteMapping("/") - public @ResponseBody Object deleteFileSet( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } -} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileUrlController.java deleted file mode 100644 index 34300ac..0000000 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordFileUrlController.java +++ /dev/null @@ -1,43 +0,0 @@ -package eu.dnetlib.app.directindex.sword; - -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController("/api/directindex/files") -public class SwordFileUrlController { - - @GetMapping("/") - public @ResponseBody Object getFile( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } - - @PutMapping("/") - public @ResponseBody Object replaceFile( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Content-Type") final String contentType, - @RequestHeader("Digest") final String digest, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } - - @DeleteMapping("/") - public @ResponseBody Object deleteFile( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } - -} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordMetadataUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordMetadataUrlController.java deleted file mode 100644 index 66e6ae3..0000000 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordMetadataUrlController.java +++ /dev/null @@ -1,69 +0,0 @@ -package eu.dnetlib.app.directindex.sword; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - -import eu.dnetlib.app.directindex.service.DirectIndexService; -import eu.dnetlib.app.directindex.sword.model.SwordMetadataDocument; - -@RestController("/api/directindex/metadata") -public class SwordMetadataUrlController { - - @Autowired - private DirectIndexService service; - - @GetMapping("/") - public ResponseEntity getMetadata( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - - // TODO - - final SwordMetadataDocument metadata = null; - - final HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.set("ETag", null); - - return new ResponseEntity<>(metadata, responseHeaders, HttpStatus.OK); - } - - @PutMapping("/") - public ResponseEntity replaceMetadata(@RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Content-Type") final String contentType, - @RequestHeader("Digest") final String digest, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @RequestHeader(value = "Metadata-Format", defaultValue = "http://purl.org/net/sword/3.0/types/Metadata") final String mdFormat, - @RequestBody final SwordMetadataDocument document) { - - service.prepareMetadataReplacement(); - - // TODO - final HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.set("ETag", null); - - return new ResponseEntity<>(responseHeaders, HttpStatus.NO_CONTENT); - } - - @DeleteMapping("/") - public ResponseEntity deleteMetadata( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - - service.prepareMetadataDeletion(); - - return new ResponseEntity<>(HttpStatus.ACCEPTED); - - } -} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordObjectUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordObjectUrlController.java deleted file mode 100644 index b0ec1de..0000000 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordObjectUrlController.java +++ /dev/null @@ -1,62 +0,0 @@ -package eu.dnetlib.app.directindex.sword; - -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController("/api/directindex/obj") -public class SwordObjectUrlController { - - @GetMapping("/") - public @ResponseBody Object getObjectStatus( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } - - @PostMapping("/") - public @ResponseBody Object appendObjectData( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Content-Type") final String contentType, - @RequestHeader("Digest") final String digest, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader(value = "In-Progress", defaultValue = "false") final boolean inProgress, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @RequestHeader(value = "Packaging", defaultValue = "http://purl.org/net/sword/3.0/package/Binary") final String packaging, - @RequestHeader(value = "Metadata-Format", defaultValue = "http://purl.org/net/sword/3.0/types/Metadata") final String mdFormat) { - // TODO - return null; - } - - @PutMapping("/") - public @ResponseBody Object replaceObject( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Content-Type") final String contentType, - @RequestHeader("Digest") final String digest, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader(value = "In-Progress", defaultValue = "false") final boolean inProgress, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @RequestHeader(value = "Packaging", defaultValue = "http://purl.org/net/sword/3.0/package/Binary") final String packaging, - @RequestHeader(value = "Metadata-Format", defaultValue = "http://purl.org/net/sword/3.0/types/Metadata") final String mdFormat) { - // TODO - return null; - } - - @DeleteMapping("/") - public @ResponseBody Object deleteObject( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("If-Match") final boolean ifMatch, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } -} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordServiceUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordServiceUrlController.java index 6b46b88..e85797d 100644 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordServiceUrlController.java +++ b/src/main/java/eu/dnetlib/app/directindex/sword/SwordServiceUrlController.java @@ -1,44 +1,62 @@ package eu.dnetlib.app.directindex.sword; +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.app.directindex.service.DirectIndexService; +import eu.dnetlib.app.directindex.sword.model.SwordError; +import eu.dnetlib.app.directindex.sword.model.SwordMetadataDocument; import eu.dnetlib.app.directindex.sword.model.SwordService; import eu.dnetlib.app.directindex.sword.model.SwordStatusDocument; +import jakarta.servlet.http.HttpServletResponse; -@RestController("/api/directindex/") +@RestController("/api/directindex/sword/3.0") public class SwordServiceUrlController { + @Autowired + private DirectIndexService service; + @GetMapping("/") - public SwordService getServiceDocument( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { + public SwordService getServiceDocument() { // TODO return null; } @PostMapping("/") - public ResponseEntity makeNewObject( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, + public ResponseEntity addMetadata( @RequestHeader("Content-Type") final String contentType, + @RequestHeader("Content-Disposition") final String contentDisposition, @RequestHeader("Digest") final String digest, @RequestHeader(value = "In-Progress", defaultValue = "false") final boolean inProgress, @RequestHeader(value = "Metadata-Format", defaultValue = "http://purl.org/net/sword/3.0/types/Metadata") final String mdFormat, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, @RequestHeader(value = "Packaging", defaultValue = "http://purl.org/net/sword/3.0/package/Binary") final String packaging, @RequestHeader("Slug") final String slug, + @RequestBody final String json) { - // Content used to create new Object. This can be one of: Metadata, By-Reference, Metadata+By-Reference, Binary File, Packaged - // Content, Empty Body - @RequestBody final Object content) { + // TODO: DIGEST Evaluate if the digest (md5) of the json string + + if (!MediaType.APPLICATION_JSON_VALUE.equals(contentType)) { return new ResponseEntity<>(HttpStatus.UNSUPPORTED_MEDIA_TYPE); } + + if (!"attachment; metadata=true".equals(contentDisposition) || inProgress) { return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); } + + if (!"OAF".equals(mdFormat)) { return new ResponseEntity<>(HttpStatus.UNSUPPORTED_MEDIA_TYPE); } // TODO final SwordStatusDocument status = null; @@ -47,12 +65,75 @@ public class SwordServiceUrlController { responseHeaders.setLocation(null); responseHeaders.set("ETag", null); - return new ResponseEntity<>(status, responseHeaders, isCreated() ? HttpStatus.CREATED : HttpStatus.ACCEPTED); + try { + service.prepareMetadataInsertion(parseMetadata(json)); + } catch (final JsonProcessingException e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + return new ResponseEntity<>(status, responseHeaders, HttpStatus.ACCEPTED); + } - private boolean isCreated() { - // TODO Auto-generated method stub - return false; + @GetMapping("/metadata") + public ResponseEntity getMetadata() { + + // TODO + + final SwordMetadataDocument metadata = null; + + final HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.set("ETag", null); + + return new ResponseEntity<>(metadata, responseHeaders, HttpStatus.OK); + } + + @PutMapping("/metadata") + public ResponseEntity replaceMetadata( + @RequestHeader("Content-Disposition") final String contentDisposition, + @RequestHeader("Content-Length") final String contentLength, + @RequestHeader("Content-Type") final String contentType, + @RequestHeader("Digest") final String digest, + @RequestHeader("If-Match") final boolean ifMatch, + @RequestHeader(value = "Metadata-Format", defaultValue = "http://purl.org/net/sword/3.0/types/Metadata") final String mdFormat, + @RequestBody final SwordMetadataDocument document) { + + service.prepareMetadataReplacement(); + + // TODO + final HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.set("ETag", null); + + return new ResponseEntity<>(responseHeaders, HttpStatus.NO_CONTENT); + } + + @DeleteMapping("/metadata") + public ResponseEntity deleteMetadata( + @RequestHeader("Authorization") final String authorization, + @RequestHeader("If-Match") final boolean ifMatch, + @RequestHeader("On-Behalf-Of") final String onBehalfOf) { + + service.prepareMetadataDeletion(); + + return new ResponseEntity<>(HttpStatus.ACCEPTED); + + } + + private SwordMetadataDocument parseMetadata(final String json) throws JsonProcessingException, JsonMappingException { + return new ObjectMapper().readValue(json, SwordMetadataDocument.class); + } + + @ExceptionHandler(Throwable.class) + public SwordError handleException(final HttpServletResponse req, final HttpServletResponse res, final Throwable e) throws IOException { + + if (e instanceof SwordException) { + final SwordException swe = (SwordException) e; + res.sendError(swe.getHttpCode(), swe.getHttpMessage()); + } else { + res.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + + return new SwordError(req, e); } } diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/SwordTemporaryUrlController.java b/src/main/java/eu/dnetlib/app/directindex/sword/SwordTemporaryUrlController.java deleted file mode 100644 index 3003363..0000000 --- a/src/main/java/eu/dnetlib/app/directindex/sword/SwordTemporaryUrlController.java +++ /dev/null @@ -1,58 +0,0 @@ -package eu.dnetlib.app.directindex.sword; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -@RestController("/api/directindex/staging") -public class SwordTemporaryUrlController { - - @Value("${dnet.directindex.baseurl}") - public String baseUrl; - - @PostMapping("/") - // Create a Temporary-URL for Segmented File Upload - public @ResponseBody Object createTempUrl( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("On-Behalf-Of") final String onBehalfOf) { - // TODO - return null; - } - - @GetMapping("/{tempfile}") - public @ResponseBody Object getUploadInfo( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @PathVariable final String tempfile) { - // TODO - return null; - } - - @PostMapping("/{tempfile}") - public @ResponseBody Object uploadSegment( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("Content-Disposition") final String contentDisposition, - @RequestHeader("Content-Length") final String contentLength, - @RequestHeader("Digest") final String digest, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @PathVariable final String tempfile) { - // TODO - return null; - } - - @DeleteMapping("/{tempfile}") - public @ResponseBody Object abortUplodad( - @RequestHeader("Authorization") final String authorization, - @RequestHeader("On-Behalf-Of") final String onBehalfOf, - @PathVariable final String tempfile) { - // TODO - return null; - } - -} diff --git a/src/main/java/eu/dnetlib/app/directindex/sword/model/SwordError.java b/src/main/java/eu/dnetlib/app/directindex/sword/model/SwordError.java new file mode 100644 index 0000000..c7c3096 --- /dev/null +++ b/src/main/java/eu/dnetlib/app/directindex/sword/model/SwordError.java @@ -0,0 +1,24 @@ +package eu.dnetlib.app.directindex.sword.model; + +import jakarta.servlet.http.HttpServletResponse; + +public class SwordError { + + public SwordError(final HttpServletResponse req, final Throwable e) { + // TODO Auto-generated constructor stub + } + +// @formatter:off +/* + { + "@context" : "https://swordapp.github.io/swordv3/swordv3.jsonld", + + "@type" : "BadRequest", + + "timestamp" : "[timestamp]", + "error" : "error summary", + "log" : "text log of any debug information for the client" + } + +*/ +} diff --git a/src/test/java/eu/dnetlib/app/directindex/sword/CommonTest.java b/src/test/java/eu/dnetlib/app/directindex/sword/CommonTest.java new file mode 100644 index 0000000..f6f91c4 --- /dev/null +++ b/src/test/java/eu/dnetlib/app/directindex/sword/CommonTest.java @@ -0,0 +1,16 @@ +package eu.dnetlib.app.directindex.sword; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.springframework.http.ContentDisposition; + +class CommonTest { + + @Test + public void testContentDisposition() { + final ContentDisposition cd = ContentDisposition.parse("attachment; metadata=true"); + assertTrue(cd.isAttachment()); + } + +}