diff --git a/api/pom.xml b/api/pom.xml index 98a16c34095a6d66f914531d96098726310027cd..54562441993b87ce306d27e02e0158e5adcacf64 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -13,10 +13,10 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.outputEncoding>UTF-8</project.build.outputEncoding> <sourceDir>/src/main/java/</sourceDir> - <apiBasePackage>${groupId}.manager.service.api</apiBasePackage> + <apiBasePackage>${project.groupId}.manager.service.api</apiBasePackage> </properties> - <artifactId>${parent.artifactId}.api</artifactId> + <artifactId>fdo-manager-service.api</artifactId> <packaging>jar</packaging> <name>${project.artifactId}</name> @@ -59,7 +59,7 @@ <configuration> <inputSpec>${basedir}/src/main/resources/api.yaml</inputSpec> <generatorName>spring</generatorName> - <library>spring-boot</library> + <!--<library>spring-boot</library>--> <modelNameSuffix></modelNameSuffix> <generateApis>true</generateApis> @@ -78,42 +78,20 @@ <!-- pass any necessary config options --> <configOptions> - <interfaceOnly>true</interfaceOnly>∂ + <sourceFolder>${sourceDir}</sourceFolder> <useBeanValidation>true</useBeanValidation> <performBeanValidation>true</performBeanValidation> + <interfaceOnly>true</interfaceOnly> + <serializableModel>true</serializableModel> + <implFolder>${sourceDir}</implFolder> <modelPackage>${apiBasePackage}.model</modelPackage> <apiPackage>${apiBasePackage}.operation</apiPackage> - <sourceFolder>${sourceDir}</sourceFolder> - <implFolder>${sourceDir}</implFolder> - <serializableModel>true</serializableModel> - <useJakartaEe>true</useJakartaEe> <useSpringBoot3>true</useSpringBoot3> - </configOptions> </configuration> </execution> </executions> </plugin> - <!-- - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>properties-maven-plugin</artifactId> - <version>1.0.0</version> - <executions> - <execution> - <phase>initialize</phase> - <goals> - <goal>read-project-properties</goal> - </goals> - <configuration> - <files> - <file>${basedir}/src/main/resources/swagger.properties</file> - </files> - </configuration> - </execution> - </executions> - </plugin> - --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> diff --git a/api/src/main/resources/api.yaml b/api/src/main/resources/api.yaml index 0ab28041db5196a7e3e168d7025f00e097e24faf..fabb6fe882ff88aa01d1d77fad2e94ae39bfa3db 100644 --- a/api/src/main/resources/api.yaml +++ b/api/src/main/resources/api.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 # version of the specification +openapi: "3.1.0" info: version: '0.1' title: 'FDO Manager Service API' @@ -6,6 +6,10 @@ info: servers: - url: http://localhost:8080 + +tags: + - name: Repositories + description: Repositories for storing FDO data and metadata. paths: /hello: get: @@ -18,9 +22,302 @@ paths: application/json: schema: $ref: '#/components/schemas/Hello' - + /info: + get: + summary: Retrieve general information on the service. + operationId: getInfo + responses: + 200: + description: General service information. + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: "#/components/schemas/Info" + links: + $ref: '#/components/schemas/Links' + /repositories: + get: + tags: + - Repositories + summary: "List trusted repositories." + operationId: listRepositories + responses: + 200: + description: "Success." + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Repository' + links: + $ref: '#/components/schemas/Links' + /repositories/{repositoryId}: + get: + tags: + - Repositories + summary: "Get information on a single repository." + operationId: getRepository + parameters: + - name: repositoryId + in: path + description: "Repository ID" + required: true + schema: + $ref: '#/components/schemas/RepositoryID' + responses: + 200: + description: "Success." + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/Repository' + links: + $ref: '#/components/schemas/Links' + 404: + description: "Unknown repository id." + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /fdo: + post: + summary: "Create an FDO." + operationId: createFDO + requestBody: + content: + multipart/form-data: + schema: + type: object + required: ["repositories", "data", "metadata"] + properties: + repositories: + $ref: "#/components/schemas/TargetRepositories" + data: + type: string + format: binary + metadata: + type: string + format: binary + responses: + 201: + description: "The Location header points to the newly created FDO." + /pid/{pid}: + get: + summary: "Resolve a pid." + operationId: "resolvePID" + parameters: + - $ref: '#/components/parameters/PID' + responses: + 200: + description: "A successfully resolved pid." + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/DigitalObject' + links: + $ref: '#/components/schemas/Links' + 404: + description: "Could not resolve pid." + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /fdo/create: + post: + summary: "Initiate the process to create an FDO" + operationId: initiateFdoCreate + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateFDORequest' + responses: + 201: + description: "Initiated the process to create an FDO. The Location header points to the newly created process resource." + /fdo/create/{processId}: + get: + summary: "Get an overview of the FDO creation process." + operationId: overviewFdoCreate + parameters: + - $ref: '#/components/parameters/ProcessID' + responses: + 200: + description: "Overview of the FDO Creation process." + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/CreateFDOProcess' + links: + $ref: '#/components/schemas/Links' + 404: + $ref: '#/components/responses/404UnknownProcessID' + /fdo/create/{processId}/state: + get: + summary: "Get the current state of the FDO creation process." + operationId: getStateFdoCreate + parameters: + - $ref: '#/components/parameters/ProcessID' + responses: + 200: + $ref: "#/components/responses/200CreateFDOProcessStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' + put: + summary: "Update the current state of the FDO creation process. Use this to cancel or conclude the creation process" + operationId: updateStateFdoCreate + parameters: + - $ref: '#/components/parameters/ProcessID' + requestBody: + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/CreateFDOProcessState' + responses: + 200: + $ref: "#/components/responses/200CreateFDOProcessStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' + /fdo/create/{processId}/metadata: + get: + summary: "Get the current state of the metadata file of the FDO." + operationId: getStatusFdoCreateMetadata + parameters: + - $ref: '#/components/parameters/ProcessID' + responses: + 200: + $ref: "#/components/responses/200UploadStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' + put: + summary: "Upload the metadata file of the FDO." + operationId: uploadFdoCreateMetadata + parameters: + - $ref: '#/components/parameters/ProcessID' + requestBody: + $ref: "#/components/requestBodies/UploadSingleFileRequest" + responses: + 200: + $ref: "#/components/responses/200UploadStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' + /fdo/create/{processId}/data: + get: + summary: "Get the current state of the data file of the FDO." + operationId: getStatusFdoCreateData + parameters: + - $ref: '#/components/parameters/ProcessID' + responses: + 200: + $ref: "#/components/responses/200UploadStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' + put: + summary: "Upload the data file of the FDO." + operationId: uploadFdoCreateData + parameters: + - $ref: '#/components/parameters/ProcessID' + requestBody: + $ref: "#/components/requestBodies/UploadSingleFileRequest" + responses: + 200: + $ref: "#/components/responses/200UploadStateResponse" + 404: + $ref: '#/components/responses/404UnknownProcessID' components: schemas: + UploadState: + type: object + properties: + state: + $ref: '#/components/schemas/CreateFDOProcessState' + CreateFDOProcess: + type: object + required: ["stat2", "processId", "targetRepository"] + properties: + state: + $ref: '#/components/schemas/CreateFDOProcessState' + processId: + type: string + targetRepository: + $ref: '#/components/schemas/TargetRepositories' + CreateFDOProcessState: + type: string + enum: ["processing", "finished", "canceled"] + CreateFDORequest: + type: object + required: ["targetRepository"] + properties: + targetRepository: + $ref: '#/components/schemas/TargetRepositories' + TargetRepositories: + type: object + required: ["fdo"] + properties: + fdo: + type: string + metadata: + type: string + data: + type: string + + Links: + type: object + nullable: true + properties: + self: + type: string + collection: + type: string + nullable: true + Error: + type: object + required: ["detail", "status"] + properties: + detail: + type: string + nullable: true + status: + type: string + nullable: true + RepositoryID: + type: string + nullable: true + examples: + - "gwdg-cordra-1" + - "fdo.indiscale.com" + - "b2share@gwdg" + Repository: + type: object + properties: + id: + $ref: '#/components/schemas/RepositoryID' + links: + $ref: '#/components/schemas/Links' Hello: type: object properties: @@ -28,3 +325,74 @@ components: type: string example: 'Hello marie.curie@sorbonne-universite.fr. This is FDO Manager Service v0.1-SNAPSHOT/API v0.1' default: 'Hello anonymous. This is FDO Manager Service v0.1-SNAPSHOT/API v0.1' + DigitalObject: + type: object + required: ["pid", "isFdo"] + properties: + pid: + type: string + isFdo: + type: boolean + dataPid: + type: string + metadataPid: + type: string + Info: + type: object + properties: + fdoServiceVersion: + type: string + fdoSdkVersion: + type: string + serviceProvider: + type: string + requestBodies: + UploadSingleFileRequest: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + 200CreateFDOProcessStateResponse: + description: "Current state of the FDO Creation process." + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/CreateFDOProcessState' + 200UploadStateResponse: + description: "Current state of the file upload." + content: + application/json: + schema: + type: object + required: ["data"] + properties: + data: + $ref: '#/components/schemas/UploadState' + 404UnknownProcessID: + description: "Unknown process id." + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + parameters: + ProcessID: + name: processId + in: path + description: "Repository ID" + required: true + schema: + $ref: '#/components/schemas/RepositoryID' + PID: + name: pid + in: path + description: "Persistent Identifier" + required: true + schema: + type: string diff --git a/application/pom.xml b/application/pom.xml index a08ddbd006343e80c377a1af01a47b6550dd4157..3d4d47c786f3fa06eed22a206cd391bdf3db523f 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -14,39 +14,91 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.outputEncoding>UTF-8</project.build.outputEncoding> + <sourceDir>${project.basedir}/src/main</sourceDir> + <fdo.sdk.version>0.1.0-SNAPSHOT</fdo.sdk.version> </properties> - <artifactId>${parent.artifactId}.application</artifactId> + <artifactId>fdo-manager-service.application</artifactId> <dependencies> + <dependency> + <groupId>com.indiscale.fdo</groupId> + <artifactId>fdo-manager-library</artifactId> + <version>${fdo.sdk.version}</version> + </dependency> + <dependency> <groupId>${project.groupId}</groupId> - <artifactId>${parent.artifactId}.api</artifactId> + <artifactId>${project.parent.artifactId}.api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-hateoas</artifactId> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springdoc</groupId> + <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> + <version>2.3.0</version> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context-support</artifactId> + </dependency> </dependencies> + <repositories> + <repository> + <id>gitlab-maven</id> + <url>https://gitlab.indiscale.com/api/v4/projects/229/packages/maven</url> + </repository> +</repositories> + +<distributionManagement> + <repository> + <id>gitlab-maven</id> + <url>https://gitlab.indiscale.com/api/v4/projects/229/packages/maven</url> + </repository> + + <snapshotRepository> + <id>gitlab-maven</id> + <url>https://gitlab.indiscale.com/api/v4/projects/229/packages/maven</url> + </snapshotRepository> +</distributionManagement> + + <build> + <resources> + <resource> + <directory>${sourceDir}/resources</directory> - <build> + <filtering>true</filtering> + </resource> + </resources> <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.3.1</version> + <configuration> + <filtering>true</filtering> + </configuration> + </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> - <version>3.2.1</version> <configuration> <mainClass>${project.groupId}.manager.service.Application</mainClass> <skip>false</skip> - <fork>true</fork> </configuration> <executions> <execution> @@ -59,7 +111,6 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> - <version>3.6.0</version> <executions> <execution> <id>copy-api-specification-yaml</id> @@ -71,7 +122,7 @@ <artifactItems> <artifactItem> <groupId>${project.groupId}</groupId> - <artifactId>${parent.artifactId}.api</artifactId> + <artifactId>${project.parent.artifactId}.api</artifactId> <type>jar</type> <overWrite>true</overWrite> <outputDirectory>${project.build.directory}/classes/static/</outputDirectory> diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/BaseController.java b/application/src/main/java/com/indiscale/fdo/manager/service/BaseController.java new file mode 100644 index 0000000000000000000000000000000000000000..0b4c61c826d3632b85f6733077c2366b2fb01f83 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/BaseController.java @@ -0,0 +1,32 @@ +package com.indiscale.fdo.manager.service; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.server.ResponseStatusException; + +import com.indiscale.fdo.manager.service.api.model.Error; +import com.indiscale.fdo.manager.service.api.model.Links; + +public class BaseController { + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity<?> handleResponseStatusException(ResponseStatusException ex, WebRequest request) { + Error body = new Error().status(Integer.toString(ex.getStatusCode().value())).detail(ex.getReason()); + return ResponseEntity.status(ex.getStatusCode()).body(body); + } + + public <T> T getOperations(Class<T> controllerClass) { + return methodOn(controllerClass); + } + + public String link(Object object) { + return linkTo(object).withSelfRel().getHref(); + } + + public Links linkSelf(Object object) { + return new Links().self(link(object)); + } +} \ No newline at end of file diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/ManagerPool.java b/application/src/main/java/com/indiscale/fdo/manager/service/ManagerPool.java new file mode 100644 index 0000000000000000000000000000000000000000..863095d9b231b8452416c1fc84e2b62d197ca968 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/ManagerPool.java @@ -0,0 +1,67 @@ +package com.indiscale.fdo.manager.service; + +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; + +import org.springframework.stereotype.Service; + +import com.indiscale.fdo.api.Data; +import com.indiscale.fdo.api.DigitalObject; +import com.indiscale.fdo.api.FDO; +import com.indiscale.fdo.api.Manager; +import com.indiscale.fdo.api.Metadata; +import com.indiscale.fdo.api.RepositoryConnection; +import com.indiscale.fdo.api.RepositoryRegistry; +import com.indiscale.fdo.manager.service.mock.MockManager; + +@Service +public class ManagerPool { + + public static class PooledManager implements Manager, AutoCloseable { + + final private Manager delegate; + final private Deque<PooledManager> pool; + + public PooledManager(Deque<PooledManager> pool, Manager delegate) { + this.pool = pool; + this.delegate = delegate; + } + + @Override + public void close() { + this.pool.push(this); + } + + @Override + public FDO createFDO(RepositoryConnection repository, Data data, Metadata metadata) { + return delegate.createFDO(repository, data, metadata); + } + + @Override + public RepositoryRegistry getRepositoryRegistry() { + return delegate.getRepositoryRegistry(); + } + + @Override + public DigitalObject resolvePID(String pid) { + return delegate.resolvePID(pid); + } + + } + + private static Deque<PooledManager> pool = new ConcurrentLinkedDeque<>(); + + private Manager createManager(Deque<PooledManager> pool) { + // TODO + return new PooledManager(pool, new MockManager()); + } + + public Manager getManager() { + Manager manager = pool.poll(); + if(manager == null) { + manager = createManager(pool); + } + return manager; + } + +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/ProcessDatabase.java b/application/src/main/java/com/indiscale/fdo/manager/service/ProcessDatabase.java new file mode 100644 index 0000000000000000000000000000000000000000..cac9265ea29aad7b809a645f9d86f4850cdc94b7 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/ProcessDatabase.java @@ -0,0 +1,49 @@ +package com.indiscale.fdo.manager.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Repository; + +import com.indiscale.fdo.manager.service.api.model.CreateFDOProcess; +import com.indiscale.fdo.manager.service.api.model.CreateFDOProcessState; +import com.indiscale.fdo.manager.service.api.model.CreateFDORequest; + +import jakarta.validation.Valid; + +class Process extends CreateFDOProcess { + + private static final long serialVersionUID = -7495201524687733365L; + private @Valid CreateFDORequest createFDORequest; + + public Process(String processId, @Valid CreateFDORequest createFDORequest) { + this.createFDORequest = createFDORequest; + this.setProcessId(processId); + this.setState(CreateFDOProcessState.PROCESSING); + this.setTargetRepository(createFDORequest.getTargetRepository()); + } + +} + +@Repository +public class ProcessDatabase { + + static private Map<String, Process> map = new HashMap<>(); + + public String init(@Valid CreateFDORequest createFDORequest) { + String processId = UUID.randomUUID().toString(); + synchronized (map) { + map.put(processId, new Process(processId, createFDORequest)); + } + return processId; + } + + public CreateFDOProcess get(String processId) { + synchronized (map) { + Process process = map.get(processId); + return process; + } + } + +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/fdo/FDOApiImpl.java b/application/src/main/java/com/indiscale/fdo/manager/service/fdo/FDOApiImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1efe25635184d56ac247ae54a8ec1f2d1e11bd87 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/fdo/FDOApiImpl.java @@ -0,0 +1,80 @@ +package com.indiscale.fdo.manager.service.fdo; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +import com.indiscale.fdo.api.Data; +import com.indiscale.fdo.api.FDO; +import com.indiscale.fdo.api.InputStreamSource; +import com.indiscale.fdo.api.Manager; +import com.indiscale.fdo.api.Metadata; +import com.indiscale.fdo.api.RepositoryConnection; +import com.indiscale.fdo.api.UnknownRepositoryException; +import com.indiscale.fdo.manager.service.BaseController; +import com.indiscale.fdo.manager.service.ManagerPool; +import com.indiscale.fdo.manager.service.api.model.TargetRepositories; +import com.indiscale.fdo.manager.service.api.operation.FdoApi; + +import jakarta.validation.Valid; + +class MultipartFileWrapper implements InputStreamSource { + + private MultipartFile wrapped; + + public MultipartFileWrapper(MultipartFile file) { + this.wrapped = file; + } + + @Override + public InputStream getInputStream() throws IOException { + return wrapped.getInputStream(); + } +} +class MetadataWrapper extends MultipartFileWrapper implements Metadata { + + public MetadataWrapper(MultipartFile file) { + super(file); + } + +} + +class DataWrapper extends MultipartFileWrapper implements Data { + + public DataWrapper(MultipartFile file) { + super(file); + } + +} + +@RestController +@CrossOrigin(origins = {"${react-dev-server}"}, exposedHeaders = {"Location"}) +public class FDOApiImpl extends BaseController implements FdoApi { + + @Autowired + ManagerPool managerPool; + + + @Override + public ResponseEntity<Void> createFDO(@Valid TargetRepositories repositories, MultipartFile data, + MultipartFile metadata) { + try (Manager manager = managerPool.getManager()) { + RepositoryConnection repository = manager.getRepositoryRegistry().createRepositoryConnection(repositories.getFdo()); + FDO fdo = manager.createFDO(repository, new DataWrapper(data), new MetadataWrapper(metadata)); + URI location = linkTo(getOperations(PIDApiImpl.class).resolvePID(fdo.getPID())).toUri(); + return ResponseEntity.created(location).build(); + } catch (UnknownRepositoryException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Not Found. Unknown repository id."); + } + } +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/fdo/PIDApiImpl.java b/application/src/main/java/com/indiscale/fdo/manager/service/fdo/PIDApiImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f50d9c426836d4cefc96daafb4f87148b507ea6e --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/fdo/PIDApiImpl.java @@ -0,0 +1,44 @@ +package com.indiscale.fdo.manager.service.fdo; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import com.indiscale.fdo.api.DigitalObject; +import com.indiscale.fdo.api.Manager; +import com.indiscale.fdo.manager.service.BaseController; +import com.indiscale.fdo.manager.service.ManagerPool; +import com.indiscale.fdo.manager.service.api.model.Links; +import com.indiscale.fdo.manager.service.api.model.ResolvePID200Response; +import com.indiscale.fdo.manager.service.api.operation.PidApi; + +@RestController +@CrossOrigin(origins = {"${react-dev-server}"}) +public class PIDApiImpl extends BaseController implements PidApi { + + @Autowired + ManagerPool managerPool; + + @Override + public ResponseEntity<ResolvePID200Response> resolvePID(String pid) { + try(Manager manager = managerPool.getManager()) { + DigitalObject resolved = manager.resolvePID(pid); + if (resolved == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Not found. Could not resolve PID."); + } + + return ResponseEntity.ok(createResponse(resolved)); + } + } + + private ResolvePID200Response createResponse(DigitalObject resolved) { + com.indiscale.fdo.manager.service.api.model.DigitalObject data = new com.indiscale.fdo.manager.service.api.model.DigitalObject(resolved.getPID(),resolved.isFDO()); + Links self = linkSelf(getOperations(getClass()).resolvePID(resolved.getPID())); + + return new ResolvePID200Response().data(data).links(self); + } + +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/hello/HelloImpl.java b/application/src/main/java/com/indiscale/fdo/manager/service/hello/HelloImpl.java index 2ff052ed516c7ca333646c4d4844e0657a58569d..c36de32d3b9c5c78b891b7dfc18d1c6a9ee172cd 100644 --- a/application/src/main/java/com/indiscale/fdo/manager/service/hello/HelloImpl.java +++ b/application/src/main/java/com/indiscale/fdo/manager/service/hello/HelloImpl.java @@ -13,7 +13,7 @@ public class HelloImpl implements HelloApi { @Override public ResponseEntity<Hello> hello() { Hello hello = new Hello(); - hello.message("Hello from Spring Boot"); + hello.message("Hello from FDO Manager Service!"); return ResponseEntity.ok(hello); } diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/hello/InfoImpl.java b/application/src/main/java/com/indiscale/fdo/manager/service/hello/InfoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d631d31767a9dd11d7178880a050a600f88ea586 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/hello/InfoImpl.java @@ -0,0 +1,27 @@ +package com.indiscale.fdo.manager.service.hello; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RestController; + +import com.indiscale.fdo.manager.service.BaseController; +import com.indiscale.fdo.manager.service.api.model.GetInfo200Response; +import com.indiscale.fdo.manager.service.api.model.Info; +import com.indiscale.fdo.manager.service.api.operation.InfoApi; + +@RestController +@CrossOrigin(origins = {"${react-dev-server}"}) +public class InfoImpl extends BaseController implements InfoApi { + + @Value("${fdo.service.version}") + private String fdoServiceVersion; + @Value("${fdo.sdk.version}") + private String fdoSdkVersion; + + @Override + public ResponseEntity<GetInfo200Response> getInfo() { + Info data = new Info().fdoServiceVersion(fdoServiceVersion).fdoSdkVersion(fdoSdkVersion); + return ResponseEntity.ok(new GetInfo200Response(data)); + } +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockManager.java b/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockManager.java new file mode 100644 index 0000000000000000000000000000000000000000..7f00e0ec610c44eeeb2f8cc7a238f5e886c0bd25 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockManager.java @@ -0,0 +1,48 @@ +package com.indiscale.fdo.manager.service.mock; + +import java.util.HashMap; +import java.util.Map; + +import com.indiscale.fdo.DefaultManager; +import com.indiscale.fdo.DefaultRepositoryRegistry; +import com.indiscale.fdo.api.Data; +import com.indiscale.fdo.api.DigitalObject; +import com.indiscale.fdo.api.FDO; +import com.indiscale.fdo.api.Metadata; +import com.indiscale.fdo.api.RepositoryConnection; +import com.indiscale.fdo.api.UnknownRepositoryTypeException; + +public class MockManager extends DefaultManager { + + private static Map<String, DigitalObject> pidRegistry = new HashMap<>(); + + public MockManager() { + MockRepositoryFactory mock = new MockRepositoryFactory(); + DefaultRepositoryRegistry registry = this.getRepositoryRegistry(); + registry.registerRepositoryType(mock); + try { + registry.registerRepository(mock.createMockConfig("mock-repo-1")); + registry.registerRepository(mock.createMockConfig("mock-repo-2")); + registry.registerRepository(mock.createMockConfig("mock-repo-3")); + } catch (UnknownRepositoryTypeException e) { + e.printStackTrace(); + } + } + + @Override + public FDO createFDO(RepositoryConnection repository, Data data, Metadata metadata) { + FDO fdo = super.createFDO(repository, data, metadata); + synchronized (pidRegistry) { + pidRegistry.put(fdo.getPID(), fdo); + } + return fdo; + } + + @Override + public DigitalObject resolvePID(String pid) { + synchronized (pidRegistry) { + return pidRegistry.get(pid); + } + } + +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockRepositoryFactory.java b/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockRepositoryFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..818c50e24a580cb02e0282e176bd73e3c161778f --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/mock/MockRepositoryFactory.java @@ -0,0 +1,97 @@ +package com.indiscale.fdo.manager.service.mock; + +import java.util.UUID; + +import com.indiscale.fdo.api.Data; +import com.indiscale.fdo.api.FDO; +import com.indiscale.fdo.api.Metadata; +import com.indiscale.fdo.api.RepositoryConnection; +import com.indiscale.fdo.api.RepositoryConfig; +import com.indiscale.fdo.api.RepositoryConnectionFactory; +import com.indiscale.fdo.api.RepositoryType; + +class MockRepository implements RepositoryConnection { + + public static class MockFDO implements FDO { + + private final String pid; + + public MockFDO() { + pid = UUID.randomUUID().toString(); + } + + @Override + public String getPID() { + return pid; + } + + } + private final String id; + private final RepositoryType type; + + public MockRepository(RepositoryConfig config) { + this.id = config.getId(); + this.type = config.getType(); + } + + @Override + public String getId() { + return id; + } + + @Override + public FDO createFDO(Data data, Metadata metadata) { + return new MockFDO(); + } + + @Override + public RepositoryType getType() { + return type; + } + + @Override + public void close() throws Exception { + } + +} + +public class MockRepositoryFactory implements RepositoryConnectionFactory { + + static class MockRepositoryConfig implements RepositoryConfig { + + private final String id; + + public MockRepositoryConfig(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + + @Override + public RepositoryType getType() { + return TYPE; + } + + } + + + public static final RepositoryType TYPE = new RepositoryType("MockDOIP").description("A mock-up for a DOIP repository."); + + @Override + public RepositoryConnection createConnection(RepositoryConfig config) { + return new MockRepository(config); + } + + @Override + public RepositoryType getType() { + return TYPE; + } + + public RepositoryConfig createMockConfig(String id) { + return new MockRepositoryConfig(id); + } + +} diff --git a/application/src/main/java/com/indiscale/fdo/manager/service/repositories/RepositoriesImpl.java b/application/src/main/java/com/indiscale/fdo/manager/service/repositories/RepositoriesImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e0bd551dcf1ec28099ce02eb577ce6c2ff70bfd1 --- /dev/null +++ b/application/src/main/java/com/indiscale/fdo/manager/service/repositories/RepositoriesImpl.java @@ -0,0 +1,101 @@ +package com.indiscale.fdo.manager.service.repositories; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import java.util.LinkedList; +import java.util.List; + +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import com.indiscale.fdo.api.Manager; +import com.indiscale.fdo.api.RepositoryConfig; +import com.indiscale.fdo.api.UnknownRepositoryException; +import com.indiscale.fdo.manager.service.BaseController; +import com.indiscale.fdo.manager.service.ManagerPool; +import com.indiscale.fdo.manager.service.api.model.GetRepository200Response; +import com.indiscale.fdo.manager.service.api.model.Links; +import com.indiscale.fdo.manager.service.api.model.ListRepositories200Response; +import com.indiscale.fdo.manager.service.api.operation.RepositoriesApi; + +@RestController +@CrossOrigin(origins = {"${react-dev-server}"}) +public class RepositoriesImpl extends BaseController implements RepositoriesApi { + + static class Repository extends com.indiscale.fdo.manager.service.api.model.Repository { + private static final long serialVersionUID = -4753402740372027632L; + + public Repository(RepositoryConfig config) { + this.setId(config.getId()); + } + + String getSelfRel() { + Link selfLink = linkTo(methodOn(RepositoriesImpl.class).getRepository(this.getId())).withSelfRel(); + return selfLink.getHref(); + } + + String getCollectionRel() { + Link collectionLink = linkTo(methodOn(RepositoriesImpl.class).listRepositories()).withRel(IanaLinkRelations.COLLECTION); + return collectionLink.getHref(); + } + + public Repository withSelfRel() { + if (this.getLinks() == null) { + this.setLinks(new Links()); + } + this.getLinks().self(getSelfRel()); + return this; + } + + public Repository withCollectionRel() { + if (this.getLinks() == null) { + this.setLinks(new Links()); + } + this.getLinks().collection(getCollectionRel()); + return this; + } + } + + private ManagerPool factory; + + public RepositoriesImpl(ManagerPool factory) { + super(); + this.factory = factory; + } + + @Override + public ResponseEntity<ListRepositories200Response> listRepositories() { + try (Manager manager = factory.getManager()) { + List<com.indiscale.fdo.manager.service.api.model.Repository> results = new LinkedList<>(); + + List<RepositoryConfig> repositories = manager.getRepositoryRegistry().listRepositories(); + for (RepositoryConfig repo : repositories) { + results.add(new Repository(repo).withSelfRel()); + } + + Link selfLink = linkTo(methodOn(getClass()).listRepositories()).withSelfRel(); + Links links = new Links(); + links.self(selfLink.getHref()); + return ResponseEntity.ok(new ListRepositories200Response().data(results).links(links)); + } + } + + @Override + public ResponseEntity<GetRepository200Response> getRepository(String repositoryId) { + + try (Manager manager = factory.getManager()) { + RepositoryConfig config = manager.getRepositoryRegistry().getRepositoryConfig(repositoryId); + + Repository repo = new Repository(config); + return ResponseEntity.ok(new GetRepository200Response().data(repo).links(new Links().self(repo.getSelfRel()).collection(repo.getCollectionRel()))); + } catch (UnknownRepositoryException e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unknown Repository ID: " + repositoryId); + } + } +} diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index 83412741b8dd224dc05a5829817bfe17ec03e764..88c4a708b92118a75f02915029d362a2799c97ba 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -1,2 +1,8 @@ # accept cross origin for the react dev server react-dev-server=http://localhost:3000 +server.port=8080 +server.servlet.context-path=/api +fdo.service.version=@project.version@ +fdo.sdk.version=@fdo.sdk.version@ +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB diff --git a/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloEmbeddedServerTest.java b/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloEmbeddedServerTest.java index 90c467a93189949235b383a3a50c1dd2987e1ee7..b03ef853684df9528dfd573f8e5f9b5001443f41 100644 --- a/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloEmbeddedServerTest.java +++ b/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloEmbeddedServerTest.java @@ -1,9 +1,9 @@ package com.indiscale.fdo.manager.service.hello; -import com.indiscale.fdo.manager.service.hello.HelloImpl; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; @@ -24,6 +24,9 @@ class HelloEmbeddedServerTest { @Autowired private TestRestTemplate restTemplate; + @Value("${server.servlet.context-path}") + private String contextPath; + @Test void index() { Assertions.assertThat(hello).isNotNull(); @@ -31,6 +34,6 @@ class HelloEmbeddedServerTest { @Test void indexResultTest() { - Assertions.assertThat(restTemplate.getForObject("http://localhost:" + port + "/hello", String.class)).contains("Hello"); + Assertions.assertThat(restTemplate.getForObject("http://localhost:" + port + contextPath + "/hello", String.class)).contains("Hello"); } } diff --git a/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloMockMvcTest.java b/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloMockMvcTest.java index e59cfaf2026a422047a16e8e1122df8e433663f9..a12e0b7dac5ffeaf5f67962542e8094d8206be74 100644 --- a/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloMockMvcTest.java +++ b/application/src/test/java/com/indiscale/fdo/manager/service/hello/HelloMockMvcTest.java @@ -1,16 +1,15 @@ package com.indiscale.fdo.manager.service.hello; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; - /** * Test HelloImpl class (without embedded server). */ diff --git a/application/src/test/java/com/indiscale/fdo/manager/service/repositories/RepositoriesTest.java b/application/src/test/java/com/indiscale/fdo/manager/service/repositories/RepositoriesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6d29c76d75611b7dfa7f0a0dde387b57be833afa --- /dev/null +++ b/application/src/test/java/com/indiscale/fdo/manager/service/repositories/RepositoriesTest.java @@ -0,0 +1,42 @@ +package com.indiscale.fdo.manager.service.repositories; + + + + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; + +/** + * Test the {@link RepositoriesImpl} class + */ + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class RepositoriesTest { + + @Autowired + private RepositoriesImpl repositories; + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Value("${server.servlet.context-path}") + private String contextPath; + + @Test + void index() { + Assertions.assertThat(repositories).isNotNull(); + } + + @Test + void indexResultTest() { + Assertions.assertThat(restTemplate.getForObject("http://localhost:" + port + contextPath + "/repositories", String.class)).contains("data"); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index d74a0b83a1c7069bccbaa3e490f331baf58a0e14..1b37a391796fe19423f5461f918923934e83fb01 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>3.2.1</version> + <version>3.2.2</version> </parent> <groupId>com.indiscale.fdo</groupId>