From 2ee815b8e214fb78b1af3049bf676baf7b7d3b3b Mon Sep 17 00:00:00 2001 From: Markus Damm Date: Fri, 11 Aug 2023 09:47:36 +0200 Subject: [PATCH 01/18] first parts to implement file upload feature --- .../InMemorySubmodelRepository.java | 42 +++++++++++- .../MongoDBSubmodelRepository.java | 19 ++++++ .../SubmodelRepository.java | 40 +++++++++++ .../feature/mqtt/MqttSubmodelRepository.java | 19 ++++++ .../SubmodelRepositoryApiHTTPController.java | 19 ++++++ .../http/SubmodelRepositoryHTTPApi.java | 64 ++++++++++++++++++ ...bmodelRepositorySubmodelHTTPTestSuite.java | 49 ++++++++++++++ .../src/test/resources/BaSyx-Logo.png | Bin 0 -> 11433 bytes .../resources/SingleSubmodel4FileTest.json | 48 +++++++++++++ 9 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodel4FileTest.json diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 4e0022aa2..de8fbf280 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -32,8 +32,10 @@ import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultFile; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; @@ -93,7 +95,7 @@ private void throwIfHasCollidingIds(Collection submodelsToCheck) { private Map createServices(Collection submodels) { Map map = new LinkedHashMap<>(); submodels.forEach(submodel -> map.put(submodel.getId(), submodelServiceFactory.create(submodel))); - + return map; } @@ -214,6 +216,44 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { return submodel; } + + @Override + public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { + // TODO Auto-generated method stub + throwIfSubmodelDoesNotExist(submodelId); + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + return null; + } + + @Override + public void setFileValue(String submodelId, String idShortPath, java.io.File file) throws IdentificationMismatchException{ + // TODO Auto-generated method stub + throwIfSubmodelDoesNotExist(submodelId); + + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + + File f = new DefaultFile(); + + if (submodelElement.getCategory().compareTo(f.getCategory())==0) { + submodelServices.get(submodelId) + .setSubmodelElementValue(idShortPath, (SubmodelElementValue) file); + } else { + throw new IdentificationMismatchException( + "The request is invalid for a SubmodelElement with category '" + submodelElement.getCategory() + "'"); + } + } + + @Override + public void deleteFileValue(String submodelId, String idShortPath) { + // TODO Auto-generated method stub + throwIfSubmodelDoesNotExist(submodelId); + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + + java.io.File tmpFile = new java.io.File(((File)submodelElement).getValue()); + tmpFile.delete(); + + } + private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { String newSubmodelId = newSubmodel.getId(); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 921231324..8af539bfe 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -26,6 +26,7 @@ import java.util.Collection; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; @@ -227,4 +228,22 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot return submodel; } + @Override + public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setFileValue(String submodelId, String idShortPath, java.io.File file) { + // TODO Auto-generated method stub + + } + + @Override + public void deleteFileValue(String identifier, String idShortPath) { + // TODO Auto-generated method stub + + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index e3c900dec..0158832d0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -27,10 +27,12 @@ import java.util.Collection; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; @@ -187,4 +189,42 @@ public default String getName() { * @throws ElementDoesNotExistException */ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException; + + /** + * Retrieves the file of a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort of the file element + * @return + * @throws ElementDoesNotExistException + */ + public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException; + + /** + * Uploads a file to a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort of the file element + * @param file + * the file object to upload + * @return + * @throws ElementDoesNotExistException + */ + public void setFileValue(String submodelId, String idShortPath, java.io.File file) throws ElementDoesNotExistException,IdentificationMismatchException; + + /** + * Deletes the file of a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort of the file element + * @return + * @throws ElementDoesNotExistException + */ + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index e18d72160..a40a6f2c1 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -2,6 +2,7 @@ import java.util.Collection; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelElementSerializer; @@ -177,4 +178,22 @@ private MqttMessage createMqttMessage(String payload) { } } + @Override + public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setFileValue(String submodelId, String idShortPath, java.io.File file) { + // TODO Auto-generated method stub + + } + + @Override + public void deleteFileValue(String identifier, String idShortPath) { + // TODO Auto-generated method stub + + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 0fced563f..cc6238b37 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -31,6 +31,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.Size; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; @@ -153,6 +154,23 @@ public ResponseEntity getSubmodelByIdMetadata(Base64UrlEncodedIdentifi return new ResponseEntity(repository.getSubmodelByIdMetadata(submodelIdentifier.getIdentifier()), HttpStatus.OK); } + @Override + public ResponseEntity GetFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { + java.io.File value = repository.GetFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath); + return new ResponseEntity(value, HttpStatus.OK); + } + + @Override + public ResponseEntity PutFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, @Valid java.io.File file) { + repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file); + return new ResponseEntity(HttpStatus.OK); + } + + @Override + public ResponseEntity deleteFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { + repository.deleteFileValue(submodelIdentifier.getIdentifier(), idShortPath); + return new ResponseEntity(HttpStatus.OK); + } private ResponseEntity handleSubmodelElementValueSetRequest(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, SubmodelElementValue body) { repository.setSubmodelElementValue(submodelIdentifier.getIdentifier(), idShortPath, body); return new ResponseEntity(HttpStatus.OK); @@ -167,4 +185,5 @@ private ResponseEntity handleSubmodelElementValueNormalGetReque SubmodelElement submodelElement = repository.getSubmodelElement(submodelIdentifier, idShortPath); return new ResponseEntity(submodelElement, HttpStatus.OK); } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index d4fd5e72b..4441050db 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -36,6 +36,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.Size; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; @@ -370,4 +371,67 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); +//***********************************// + + + @Operation(summary = "Returns a specific file from the Submodel at a specified path", description = "", tags = { "Submodel Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Requested file", content = @Content(mediaType = "application/octet-stream", schema = @Schema(implementation = SubmodelElement.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/GetFileByPath", produces = { "application/octet-stream" }, method = RequestMethod.GET) + ResponseEntity GetFileByPathSubmodelRepo( + @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, + @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); + + + @Operation(summary = "Replaces the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "File replaced successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SubmodelElement.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/PutFileByPath", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) + ResponseEntity PutFileByPathSubmodelRepo( + @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, + @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath, + @Parameter(in = ParameterIn.DEFAULT, description = "file to upload", required = true, schema = @Schema()) @Valid @RequestBody java.io.File file); + + + @Operation(summary = "Deletes the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "File deleted successfully"), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/DeleteFileByPath", produces = { "application/json" }, method = RequestMethod.DELETE) + ResponseEntity deleteFileByPathSubmodelRepo( + @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, + @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index 61d8f2710..64377b7fe 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -27,11 +27,21 @@ import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; @@ -41,6 +51,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -164,6 +175,44 @@ public void deleteNonExistingSubmodel() throws IOException { assertEquals(HttpStatus.NOT_FOUND.value(), deletionResponse.getCode()); } + + @Test + public void uploadFileToNonFileSubmodelElement() + throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("NonFileParameter"); + assertEquals(HttpStatus.BAD_REQUEST.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToFileSubmodelElement() throws IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("FileData"); + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); + + } + + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + java.io.File file = ResourceUtils.getFile("src/test/resources/BaSyx-Logo.png"); + + HttpPost uploadFile = new HttpPost( + getURL() + "/basyx.examples.test/aas/submodels/FileTests/submodel/submodelElements/" + + submodelElementIdShort + "/PutFileByPath"); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + uploadFile.setEntity(multipart); + + return client.execute(uploadFile); + } + private void assertSubmodelCreationReponse(String submodelJSON, CloseableHttpResponse creationResponse) throws IOException, ParseException, JsonProcessingException, JsonMappingException { assertEquals(HttpStatus.CREATED.value(), creationResponse.getCode()); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..da613e94c07c4fef1b8a454ef927fd2fbf987f66 GIT binary patch literal 11433 zcmaKSbyS<*vS{$&?iSo3SdrpZ9Ewxii-jOXid&&T@gl{G6^G(ZDDG0MSb(BMf;)Zu z?mOqc_s2aqYbD>ecV=eKo|(1x*hg*6SGZVISO5S3S4CMt=lMDG{M81cKmVG#iq}3r zD7_SoymZ~{y?m@Z>;SU1Zq{}n6;~?)g~d_N$jeAWUEIdamDlPY z7+zmj=rc9|AR+AwwX$)x^8#7hIXFTj8Bf}~89|P=l8lDJ8ek2myxm(z<@X+Ty6-jh zY~DNDh}kkqOMxVO#h(ed+Id-ld|h22p5nfejQ>Sf{Q3Q#X+B2Ke}Q;8OEUgnri?VS zLGo@McAyu$U>+MVSO6p<#>@XgNazKa8zcZ00P}%``S=BR_+N;Fg~a(qK>zwMKCk9s zYcH;&p!Bb`o=1|5Z@s*r;(UBQK0drYg1l}X4t)G#Vq$z?0X_i%o@WRiPd|v4l`jv( zlj+|S6zn{0JRG53j&2apKNPL3-QIagGCp(qf4ATY{co}m&wq*OSzvs=R!}~EUhqF# z`Zu74#{UlL>iXZ%o?be3|Bv7Qk+7$pAJmRd$IjF3orle{;_R9JSqdsH?_p=<<>sO1 z=H~KmDQdrU^K$ci>jni0@be0Q*fp$d93lVAaQqjFhK9Hb#M8?PVq>SGAj$YlgxAs0 zRvauKC@8`Y7L->~5aZ`pl$8~eQ&8d;ej%X9F9Mboko`BVf}71dS38K;zj1BWG{BylDpGb2b-0ZlzATf67^m$A+Ma)N&AkWJp3~zUA}g z6zR6kUIyKdn-UK0{{*~5K6wZlr7f*G84rFEJm2YLq5QyLWEJ@tAy=o-gT=)y^E^NU za#hv>tRhLtK$W$<$PuIlMj^GmSl9#xMpj6@{{s3S{%mCiYzh%ZEKf9Xa;3*j?FM6g zKr{q#yvqsT>YufsmOZl-Vry+k5WG%9*CE=q&C2*3YdmzG3GSzuORayHX7<^dSHG!= z%fFQiJB`LH)t4JddAIWYm5;l}d~^Ny*oX)o=veneHOD_F1(xA&1I*O}NN=!)X#h(} z!zx&qR`BT9uFJz$`V(B4`s)muT8&$OBlS|Wu{-n^p7v0wpzKo@5E3YOKL zb4TpCJK-0pHmlntkAaT9b#+)3@V&Ee3tbxu0#M0G&Qu)I`O+mU3wH1+CNYGF&VFEi zz)kpZRs2xLnET^MGM7Pt@%7d~#R6YUoZh9`2!Qg~@w&{1V{jr$A!3%B;pR+x060h! z2Iif!g@vwjgmDQF$Gw~|zWq&o+>}GzG8*L1M=DtA+KVqc%gUQB7=eD)QHd5kx=pm? z;>{2e9tDs^+k&=4%sFY4E!LlGcXfsKj?P!MxpKBqRkokSVIKy9GPQQL#(&Gv53VeLDzDey}XN2*@HokEkR!ZSV%cK3J zK=u~Dd*~MViN(V3ciakV@ETSQvw{RKPWII4nd$ zLKGRWaU-(sle~p)`Aed6&TwhnxH+O70eiU@_Hr#ZBtc`0i{008BP`i}qy104jIxRWrCMbqyD-7=OYyZd^>}N53fgQZ%?^*U+5!a^GNV7E72;9Agf36lo{}2;q`t@Kc$s}IS@b4#i z_l`dJPg_tTY(7P-| zxDSAg79xxOf;7m%cZg!oT6R(0ANapX_m?A(Zladv|y_v#)>Ke%G&zLn44|XpKb-?sM@n4?!XN zU}xV@>(V{}Y!+RBd^E~w6yKF4oOlr#*);iL3UzCvO*wQ(QzgW#<>6y!cmeR9Kvrox zP}RY#&b3@9RgPgHOilmDyS>wxws1IAT>p4HUt|o^w%k@{e|T{*!=}>70u(ZJej;*O z{a}dAq2>`{%_-B`Z}nXth=?+fSapgUNhsbZCSKlEyRIA{9AL)6#*zpEgH8Hx zW?PG-i>ZXr_5Qihqs?Fnw`{gLWm_GzPb@1t3`Q&gCKw5B?A5x1$meqA?$Bp3%5 z|1~%wH+7#rNr-ZOo3FqZ&MZW#At5X1+nqh=qXUc!jYjMBlenmRPw7t8oYjAu$dx4f-F9^pz`;MNia6t_X~WK zMzxK2zR4W7u4C8AuI$97h(ku->hJG|sA#B2`L3lYyD5LG zaC;!cv+wn5T{6z()1#LTlGoK8b?r%%x56R7|5fF9MCuGl9knGcCp-D`N9*yT-qo#5 zl1|9zIPtKUr200B&>&IOgde2L?c45mpMuTwMY>J3r{HVPnXVL6J4|p47GZ;Tha;My z4YmA5o{4%K$>7+lcq(DA`OVVXW_|=iM0GYTJLAA$%&M8~>qn(nCIZAOo>(LRqUqr} zg{PBhoO90P+z~;pUvgu&Aukr1}w0r(;)0J zME4+ea=XbE=~-wlB6=n!*Huq2*eya^K~65fO4Va#cL)&uiU33ss%29e->%A4YT9pUUU<$)*@S6J}td z8eUquyKiJM>CkH8%S$X&S)P4c*RPKDJ1XXUsx+XqiO&b3Q}l`G&{5zpPb+4KiX#3p zKgfTSWX;D3J(t>+7e;%1Lf39uUsSpF`(wrk9Gp468480L(`d_irR#Cdo${v+I5uT$ z1scD`JNR@>u?J+zn{@jWA0tjiPHjR-Obm;x|-XrS8WJ0YSGTB7XLgGvzc)XTLhUxYHPTUrt#gT-ImON8X@ z5b3o&=F=&(+Opje%bp#k3IS)1?Jwrjd}%5zE^(n3u0@eH;!N`X!ZSY>ann)-=lqP4 zAJ0J&<4u=dLHto+eoCC3{(SMqzFzEmX|oS2`_kp};qEN6f6JJZs9n)6qJOx$@csRE z3?a%_e*S6F(32rO|0{zo=FiIW#YM~u0kp0uDJh+o%9nM-JpqE+u!BF;5VY9tzowIh zhKAj1Z-wmhd*w~Oly2`la3Pv6E*cJ!H+x@lJ(J*tCsZ!|cq$VQ_&r%y$3M64SY}o4 zr=c<45AD`M_;n?qDLnZghRPcsk zt)fOXTY$SSJ%g;|%iFnLtj~SHoJLuYPrN|2#^dwUj>}30v&P34LRV^TZm0cIyCqg8 zKVA~V8HbfwNsR0>eC2$fV;HcqJMyIv{*xK2>~&zk*!<6I(OdcaVTFcl6&1|;+TM!_ zosfZnS-X2q{0Cxj3)?94F=e%TpN)>znz(my3Y=nZbo6fS#p)fNUNT5MfsJ~23$!gG z@Vq`L{e^3`AV~~l?Cq6wIJVL&DV2LSHv3c}jii62o?4d%Q-`z~GZH$7C!`D)nT_*Q zsmaRf5`uV}O^~=fQZ!3HAytS1s+T}U@2?Gq7$Nojy+?=2Z5|G);nVkBLtv*R?6dhvqw!mcjx$B$HGSY=err zm3ZN~S$Ps;K>MeFq~ta}rIj#_p%+t#IJt@n2~Wyq7f8uGs0ihvRa(Wkc2X)yrh0`Q+YtzO$ z#(s!&%cR^7o@%u4=CCum4wv!rELp+o&1|F*JQB~+jU zA3oqGdYl)?R~i)!^^EO+imImHD^_}K!%^sw1^1JNbfw$d8*eqW<(V~e4XK;`VT4N( zS%tow04lwdtQI@DhNr}nz!%RpL(57s%TDULMZ(l5(em#nu71dHt6b5LP z>tVv(Tsp;0_(x1{!)a-ca#)n=#lnfd?tQyKn7R{KD4?V~iri#21=pP$zw?=VBN6;f zQ+9dY)P1SB8bkg?HU|@ZWn;3|!tzuHV(Lhen&!t`%hNi?mk=Q1>Qdre(T3YXRr4`- zU5aTh(i)kfZwH7Wpbq_Qdsc)|_;GPz75;G0o69f0x`H(LmA1r@*;yji=LA@L68_r) z`@P9PO*K&}oS)VkKf+JeP)%^Xz}Eo+AJ-aQsKjhMaEkhH1LS1yOj#R}fBj7TEuu<3 z5v<3UghObNlCK!}C+FJRsqt5_QFU%A!QYJmTthQjh{eU@DA(1~s3q9`)OTg0mA>nH zpvVS#B-3O_49rV_y?~J9N6*%2Bm4^Y8)K^I4_(~21*A}?6rF;%avzYx!MrH0%TL=W zjim>4GTY(jK5NiN;rbqJb;c)F@H4_azYv$M0$whP!}bFPvA zAwwwaa0@%=w9T1nA~hE*h)>73*7<{{9$HRsaaDLViZVA_VNV32YYdhyG`t&(FJcXR zR!V(qJ5nZUiRe;D;_>mLr~c?f#JYp){O@w?HULCU8X(_s)X0erZPk6gC6I3r7YgoP zd~9rBRZ9z^*@8{<;S?t+IlG~-raPE}b0l#s(W-t~1=0O#%1+aJ{;4nGCd_8gIY`fN z0TEa@oQ(khyCcyeHt<>=T}WKH2nvx-|=_xJaYszTeQHmsFHtl}J-+j-mX zV89nnexLeTk)vkkiX45u`!MVCHK}gNMdkAH(}H{lVnrcOzi@x_@c7sm!)0e&U_6`u zvA=F-tw-`54=>lF6yV%bJuB+F1&B^XIa->XF!mmwrtamLYLjH3T2!;NT^de`r$EfA($ucTV8^W-XDgWVw)2&@;UH5oJr0F3 zWo=B#>F&dz*a^Z7xmN>Gqq5U2U^whZGun@pY6N!+z1Y8nJjqJd^s@vk5vi8Wj!V0L zztY_6Qvd?+?^a4{9KB{Nqg`BG+sQuDaMye8#66^g%rRe@qpEHwYpVXrOBXpR{+U{o zK!SD4QJWFYtg;06Q?_KFdQpu{0!C!FpgD~xuL}b&jsmNxh8tfVPXCg1Md}k432Ser z3p$C9k9Pvg93AfuPdmM?*AH)Og!APoIE*XQL4mZj6l>7ogMv6`~#2Eb8CSb#APv-AxA+ zoEx%L5|M8;uB?F4IdLjLvohL8{ECDSHJooH4dKMwx78O8QRfj^;-!8+3FV6t#T6XH zJ}9I4n!TviZc+x(QNxlhtWAiYzNfxkM5*CGmH1dsl6~a7)w;~O85UC0j#jxliZBac zSVWmbxu8Wu@Y@tfGdn~k*T zI3_@S7w$a{B2=FQdlLRCR~-kvzbq$}#((DuAed}!d>4QbEJA@(|K*Db?_^%?^|bFK z5K_=kTztvOJI<2LYn$+}5YozO8$rg)ew$TNHs}d zW~wbPoX(RRYQa|=Mp)}k441`H;_I{|xniYC{^lTTOtC(9|9nI%ac)AG_HTFMnfJ40b zIL#0)h`K5K`vlsGrTFk7QFPRGx2&|R^J(8=!P2ZR)#!lI0+Wa`#LCK{2dvP&Xn`BtWC?)KUJN6S!%jzq{- zBGpA1EBaPWqi!Y1oB%n(2z&7uXDr5)1@WXJLIOm1Nkk-6u;N)xeSh-;=xk!yB#xEC zP!kgy1cA(qmbi9B(Vlx+f{{MKY?&rv`UBFn?!?4In&yFXZJ(Y~JV%*3J8AyfDtC8x z>A9k>+pWxD+1CT&Yc5U7Jpx+vG&ExekX>Ty`?$+CXu9oTRM+IY7kKyi(^sHk> zzoSFm4yMj%5NYWw(HQl^12kd{^23)5L+$uf)HKAvoOiT%n|;H&&Ax)~_6UoLA@QF+ z?q`olqAX{0HNM{-r*~a?gCrAA#YY{5B+;j?)zcI>MgbLV<_1aDkVm)G#BWiZNI-`F zG$p!=5|S;-;lQI&ue8JD*?Kn>0Z~Kvcy6~ZeQV93zP;h$R}dpe%)5_JJU~oDevAF$ z96B>QE2T(1!{_TgLBR+u7EF$1JsOo)6?z73h}S^I8q@_Ql$Z6zjhA1h0m!L&K@E2| zfjP7}f5ynvkOf|YZs;|rsHi4AaHoba(U8@#j1ccb^_8B;kzv6moBD=9_gh+0e=tPO zOW!if41e9Y-v%TaPH2+Z275HzE%Jv?3sJ}7Y~1tMAQcrix@HxigO0Z2e)wMI!n~4nm2Ncm?!)>Afi)4 z4nY}(*ZnplLBgwkZor+8?vnM4vDZ zceN;>+y+@A_CCxSpI@-^m?2P6rw)3EF9@n_sKLvne@U*=hB(|7+Z{n~>E;mvr81>OVP5<+Qpl z-#H99J-sfTC2N|F5p9v6?_mIXRG=1yhvVSmsV5N_UG#22Daq?cj0!F6Dz~r_SWQKk z-xQ&%E)eOOel0A-TM!ZQ{Y?3A!4CL{6{L%w&M(x={j1LN`cFPZhfJG*T$x02cJ>9w zyrl~BCfyz=TsmdsW=oQvJz7De$k4J3lidP`cO50~l)!S}P31hZc5ViT74z>i*iFk~ z%ud?;pSqVDPcup?w_UoPU$4kB93wOg0uOJ-;mDFyZS)~**qfiZlKAVY&Pp`=H%w2e zI;>CRb8^i7+&t8!f9iGqCm08|Opxl7^$A#o;5fbOJ3hm*GBJsY8!cvOEt>G|t}b_q z679du1Q$;!%?NyP_ngF}0pbuHhXB^uNawEqG4VD^ebUxbZ1q54w$0>OG`6L&T=Al!A`YUw9iAwPg8? zuEnj^G-Z?G!bq7He*%USJRK4A7*E?S6>##HV2<`z(i_XBkCH+;+@VNi{oEf7`8p0J z>&lV#b_+h6y;M*4#X?7d0UDR&gC6-?qtT~galm61C0SbXL>~|(LhD**lp-5igZtDD zd8@qkYd(l+K(Iv|7xjS;J2FhA`?GrKtm4s9`-$HdIx(z0+AT~^ocNOPjaIwKu94+| z=i_pN>8C@U8+rRGFD83M1n|uj28odigQH^DEWE{LZ?Aw_{j#(#BCA)Cnsvt@G*9+) zpC)SHtOs6wJR0{-sl}=~ylC>xJ}O|pPquoZx;krp^#L|5|0=#ZCwUJl-XChpW#q$V zq_a$kK5ri`+^4t;1Pio1Jamlr6?1&wh!d2L!&+-uTul32)OQWGmtS?2SoMls-JqC& zy?BwkW?N^}(Sdn1_Z-JxPT0Dkq8#&iAKq0gxeR038POmUd{n^TNXBWuV-i?SVw#)m z3H;Osz-Vj_z;6uxE6c!Mx`5BB#CyMG)&i6DC?c4SPBZx>9#rzu3;Lljm>|^x=#C~W zAWhh? zVQ{O+_yIH#-uN&yNT1_puZ^w+-k*fDY_&~Qk_upe{TB59%$vV(-idn! zu6miA-C6w|x^Nt`aCeW<`<{mcRm*zsE#@IR!}G>G=MPv#SIHI?$c_ZskAKovwGQi< z@Sy<+*7_;fZ3%y2;}DIZ^%tKXt(HWg`}kA}h)}J>|4&n;JQz#5v}HjNZkjqV-n ziwyhw2Lg7^9G(Cq=IJF>&zFE^Dc@Q8lXaB?n} zJHf~Co}YvNR0GwzC4?4`ciA7GGg)XNfea$}8J%B0=}{4l_+yRhrkPA+Mf1IHrrJ;0 z2ksl%=@8QEjz?sPLz53RF%#KO@Ln&jIE?qEq_o9leTa!+>e({r7@)yzi^uU)?@dW z+cD2hqXf5C(ewQgE2wE z^QQ?g5EEJ1RsBbW{qNxBy@V!-%4Ng9;RW(lJ_lFLPde@GRbw;OUPLsxvx8I{26{B4 zG%xahWMq6h%RW4uEG6(PGB{ki=JZ0Bmf6V6+&{rMA*L*I`Y-{#=Za&>`eI^`_viA% zhl>%~mp;;`$NuzsD9Q#2=mec>1nxIO)6@-b%*G7sdIM`ix`Sx#W5Af9lYC^cT16$y z=?OljYYo|xsjAynCts{Vr2f~B#ifSz=v>aHxphI#->;G3z9W|4F9&^2 ziw*dCU9Rig$hIiy0!?*J4-2bic4c5?+uhAw{Y*!{;yI2^SrKl))4qMZG3;E-6PfYB zdG3ZmW}{ts#Dq5)KJ_vMGlvkx$C9G!Q_*AxhVAb#cPsKwKhW@MrO@}TZu?`&q?id1 ze;v58Et!xnv1CsLdVaZXDT&P!k-1%FhJWdbEG&quJ*#?Cn{su{Y56se3WqgbcAhvsEh=DA2z-)uoYXpUVAQqzn zZ~TW)rMa8w2bLS)_o1QaM}llErx$x0OmiX)O--v4*n%}EFGe@ND>kdKBohki%+BiI zlafxfcYFK|4USO5u*o}G4u(e#vFp>e*y8@!0)<%0TgwIL`}KEGKKthcpLOE-L?}Cq zE`d-2+5QgzEQIj49@XOl7s3aX$Ym%C-bV!;{7+9?E7^yblM3<@;R0rBRmUGg;I{$J zX&rXA`2bNaO*B~_BHp=hre8oD?k@R$0?bI-3d8CaQgUw5WvtWHS&w=PK>8j%V-m2+ zcyo8Tn|spHIT(-R_mAl4XnSpTl&&I->*hDELfKkYV5*eo5@s)N%;qCNt_u}n=a8|WB9;hB~ol>aeK4x@*#@Pun56G&V z+4nM@+8}Ya&mH&%2laNZSqee3`{|~XX%-b%51K1FU5 ztTPwN;F8>p+)$E}*Sl_?*)=P^des>DvK(p-JBLrEH8wR{@R0zu3!tmtqMWtn{v!9R z;48_f&C<|N=gV%e!1-Q z4>ThHQzPc^pCV_cc1z$L!%%e(Ew>ZZliLS(sW1#9%=P?j&_p$nk`w$#C(w*n1hZ)o zTN_W&pY6>?y$vfcRn?@;@(D#z@tjqkkL9!KrK4k6O|FFgwR5iYFcQuJ`tia&u_T5B zlh5vP47rR2qr7aSItI?&>84_FFqM{t0k_Hf>z7&Uc_?(VW7_1Ot~6C=C)XP*HZvOy zD_e8$C{ud9Oth@!db9km^?8xXEl_e;uk#|L@kHe;5(VvL@NiM*XH`aQlec13$=9SZ z^}$oC&_!BibQ=p-#6gas14hLRjc8*y{< z^C~`GadmaI!kq!7a5`d(pV6X|M64tG(_beNc3n^x&Yw45Bk_wW_^JEABupu)TBRi= z9m4)smE=sRF~^>c2ozjQtj%ObfIzR zAzx9ifkvYf=QW2E4`Ugo02}xbc9jwH48SN0CR~98Fn|Tlo%wZ~$6zvex1b|m_&mhf zt~6|vFP)yc^AL-X9h;z{Q?pqYXtv4^Nca+}+L2P!s_+Q^oSA9UtC$^+@ciJiLQb#pP{zhrFj78te+U3~7N&xfpds$-w;E z5)b2IH##3Dc2>jR99g^IYwrYCnfTw1>vo&JrmV~^tanowwtxa9N0S>vRXY>{=`M)V^BQ(Q4yTu9RLOAblQYJ@G+_&H*>O`6~+6qtU$)JKd?BV>eyq~ZBt zhn!qe2kbK;-ID{YFFzYk;eF@h{hZx;Z}GW4d}w?)Xv5kMWnOI6?zy76+?e%Z#ne>j z#M~0dRKoFioVJj27LxZ#@W|TwtMpRC#sMR=>}euwD~qeKu@rLYOHmhm@u&xZOw7k_ zuTA=WMaKJ?%4>>v|HjGbw(AiiXkm4=b8Y)9nqT1A@u7`m>DMA59rs5wMA+#6&f>~3 zI|$(R#2-t_6!sj5wN4uHu3M4zV>s}EUP`_5xxRRI#%Rj)tW~46M43}m8Y|xi)GJoZ zDLyxn3=h8VET9;)dHV(ObalBvGPLdSD1!Ce$!A?>)MsXP&~Fs?9A&gPy}KSO*_qFR z&1v?-UeZKb=(QnAo+CsN-jdy~OVxJUa>^DK!M#x?O+mowW;Xy)fNOQlAzJA&k(ikA zq?rrc5~+6t?LrZF5*vg0H-v3s7nXriN=H7CbsvRxu9n+IK_C0vNJy^G$5;n>1G$n8 z;ctOAW$6=gRvB?uYN@L)QWxmqnKraI*gW?YQkt Date: Tue, 12 Sep 2023 10:16:15 +0200 Subject: [PATCH 02/18] Add partial implementation of getFile for in-memory backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../pom.xml | 11 ++- .../InMemorySubmodelRepository.java | 72 +++++++++++++++---- .../MongoDBSubmodelRepository.java | 2 +- .../SubmodelRepository.java | 5 +- .../feature/mqtt/MqttSubmodelRepository.java | 2 +- .../SubmodelRepositoryApiHTTPController.java | 18 +++-- .../http/SubmodelRepositoryHTTPApi.java | 43 +++++------ pom.xml | 15 +++- 8 files changed, 122 insertions(+), 46 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml index 2e0148c9e..66664ad2c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml @@ -1,5 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -45,5 +46,13 @@ org.springframework.boot spring-boot-starter + + org.apache.tika + tika-core + + + commons-io + commons-io + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index de8fbf280..61763ac25 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -25,6 +25,9 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -32,6 +35,10 @@ import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; +import org.apache.tika.mime.MimeType; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; @@ -44,6 +51,7 @@ import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; + /** * In-memory implementation of the SubmodelRepository * @@ -54,6 +62,7 @@ public class InMemorySubmodelRepository implements SubmodelRepository { private Map submodelServices = new LinkedHashMap<>(); private SubmodelServiceFactory submodelServiceFactory; + private String tmpDirectory = Files.createTempDir().getAbsolutePath(); /** * Creates the InMemorySubmodelRepository utilizing the passed @@ -218,40 +227,73 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { @Override - public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { - // TODO Auto-generated method stub + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - return null; + + return new java.io.File(((File)submodelElement).getValue()); } @Override - public void setFileValue(String submodelId, String idShortPath, java.io.File file) throws IdentificationMismatchException{ - // TODO Auto-generated method stub + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream ) throws IdentificationMismatchException{ throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + if(submodelElement.getCategory().equals("File")) + throw new Exception(); + + File fileElement = new DefaultFile(); + String filePath = getFilePath(idShortPath, fileElement); + java.io.File targetFile = new java.io.File(filePath); - File f = new DefaultFile(); - - if (submodelElement.getCategory().compareTo(f.getCategory())==0) { - submodelServices.get(submodelId) - .setSubmodelElementValue(idShortPath, (SubmodelElementValue) file); - } else { - throw new IdentificationMismatchException( - "The request is invalid for a SubmodelElement with category '" + submodelElement.getCategory() + "'"); - } + try (FileOutputStream outStream = new FileOutputStream(targetFile)) { + IOUtils.copy(inputStream, outStream); + } + } + +// @SuppressWarnings("unchecked") +// private void createFile(String idShortPath, Object newValue, ISubmodelElement submodelElement) throws IOException { +// File file = File.createAsFacade((Map) submodelElement); +// String filePath = getFilePath(idShortPath, file); +// +// java.io.File targetFile = new java.io.File(filePath); +// +// try (FileOutputStream outStream = new FileOutputStream(targetFile); +// InputStream inStream = (InputStream) newValue) { +// IOUtils.copy(inStream, outStream); +// } +// } +// + private String getFilePath(String idShortPath, File file) { + String fileName = idShortPath.replaceAll("/", "-"); + + String extension = getFileExtension(file); + + return tmpDirectory + "/" + fileName + extension; } + private String getFileExtension(File file) { + MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); + try { + MimeType mimeType = allTypes.forName(file.getContentType()); + return mimeType.getExtension(); + } catch (MimeTypeException e) { + e.printStackTrace(); + return ""; + } + } + + + @Override public void deleteFileValue(String submodelId, String idShortPath) { - // TODO Auto-generated method stub throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); java.io.File tmpFile = new java.io.File(((File)submodelElement).getValue()); tmpFile.delete(); + ((File)submodelElement).setValue(""); } private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 8af539bfe..4fda2f6ea 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -229,7 +229,7 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot } @Override - public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { // TODO Auto-generated method stub return null; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index 0158832d0..ebff2b086 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -25,6 +25,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.InputStream; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.File; @@ -200,7 +201,7 @@ public default String getName() { * @return * @throws ElementDoesNotExistException */ - public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException; + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException; /** * Uploads a file to a file submodelelement @@ -214,7 +215,7 @@ public default String getName() { * @return * @throws ElementDoesNotExistException */ - public void setFileValue(String submodelId, String idShortPath, java.io.File file) throws ElementDoesNotExistException,IdentificationMismatchException; + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws ElementDoesNotExistException,IdentificationMismatchException; /** * Deletes the file of a file submodelelement diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index a40a6f2c1..3a28adba8 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -179,7 +179,7 @@ private MqttMessage createMqttMessage(String payload) { } @Override - public java.io.File GetFileByPathSubmodel(String submodelId, String idShortPath) { + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { // TODO Auto-generated method stub return null; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index cc6238b37..8636c5e0f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -25,6 +25,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import javax.validation.Valid; @@ -48,6 +49,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; @@ -156,14 +158,21 @@ public ResponseEntity getSubmodelByIdMetadata(Base64UrlEncodedIdentifi @Override public ResponseEntity GetFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { - java.io.File value = repository.GetFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath); + java.io.File value = repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath); return new ResponseEntity(value, HttpStatus.OK); } @Override - public ResponseEntity PutFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, @Valid java.io.File file) { - repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file); - return new ResponseEntity(HttpStatus.OK); + public ResponseEntity putFileByPath(String submodelIdentifier, String idShortPath, String fileName, + @Valid MultipartFile file) { + java.io.File destinationFile = new java.io.File("temp"); + try { + file.transferTo(destinationFile); + repository.setFileValue(submodelIdentifier, idShortPath, destinationFile); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } catch (IllegalStateException | IOException e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } } @Override @@ -186,4 +195,5 @@ private ResponseEntity handleSubmodelElementValueNormalGetReque return new ResponseEntity(submodelElement, HttpStatus.OK); } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index 4441050db..9e5f058aa 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -51,6 +51,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -392,29 +394,28 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( ResponseEntity GetFileByPathSubmodelRepo( @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); - - - @Operation(summary = "Replaces the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) - @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "File replaced successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SubmodelElement.class))), - - @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @Operation(summary = "Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Submodel element updated successfully"), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/aas/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/json" }, + consumes = { "multipart/form-data" }, + method = RequestMethod.PUT) + ResponseEntity putFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath, @Parameter(in = ParameterIn.DEFAULT, description = "", required=true,schema=@Schema()) @RequestParam(value="fileName", required=true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); - @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) - @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/PutFileByPath", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - ResponseEntity PutFileByPathSubmodelRepo( - @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, - @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath, - @Parameter(in = ParameterIn.DEFAULT, description = "file to upload", required = true, schema = @Schema()) @Valid @RequestBody java.io.File file); - - @Operation(summary = "Deletes the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "File deleted successfully"), diff --git a/pom.xml b/pom.xml index bfc87f7c1..cc638377f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 org.springframework.boot @@ -245,6 +247,17 @@ + + org.apache.tika + tika-core + 2.8.0 + + + + commons-io + commons-io + 2.13.0 + org.eclipse.digitaltwin.basyx From cbad8eaf85d2af058aba43a42bf0da3fe6854937 Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish Date: Wed, 13 Sep 2023 20:32:26 +0200 Subject: [PATCH 03/18] Implements File support for Submodelrepository MongoDB Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../exceptions/ElementNotAFileException.java | 46 ++++ .../exceptions/FileDoesNotExistException.java | 47 ++++ .../InMemorySubmodelRepository.java | 18 +- .../pom.xml | 4 + .../MongoDBSubmodelRepository.java | 231 ++++++++++++++++-- .../MongoDBSubmodelRepositoryFactory.java | 20 +- .../TestMongoDBSubmodelRepository.java | 6 +- .../src/test/resources/SampleJsonFile.json | 5 + .../basyx.submodelrepository-core/pom.xml | 6 +- .../core/SubmodelRepositorySuite.java | 73 ++++++ .../SubmodelRepositoryApiHTTPController.java | 1 + 11 files changed, 423 insertions(+), 34 deletions(-) create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java new file mode 100644 index 000000000..59fed0ddd --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.core.exceptions; + +/** + * Indicates that the requested submodel element is not a File SubmodelElement + * + * @author danish + * + */ +@SuppressWarnings("serial") +public class ElementNotAFileException extends RuntimeException { + public ElementNotAFileException() { + } + + public ElementNotAFileException(String elementId) { + super(getMsg(elementId)); + } + + private static String getMsg(String elementId) { + return "SubmodelElement with Id " + elementId + " is not a File"; + } +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java new file mode 100644 index 000000000..bc1ae701f --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.core.exceptions; + +/** + * Indicates that the requested file does not exist + * + * @author danish + * + */ +@SuppressWarnings("serial") +public class FileDoesNotExistException extends RuntimeException { + public FileDoesNotExistException() { + } + + public FileDoesNotExistException(String elementId) { + super(getMsg(elementId)); + } + + private static String getMsg(String elementId) { + return "Requested File inside File SubmodelElement with ID : " + elementId + " does not exist"; + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 61763ac25..8fcff4962 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -28,6 +28,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -62,7 +63,7 @@ public class InMemorySubmodelRepository implements SubmodelRepository { private Map submodelServices = new LinkedHashMap<>(); private SubmodelServiceFactory submodelServiceFactory; - private String tmpDirectory = Files.createTempDir().getAbsolutePath(); + private String tmpDirectory = getTemporaryDirectoryPath(); /** * Creates the InMemorySubmodelRepository utilizing the passed @@ -282,8 +283,6 @@ private String getFileExtension(File file) { return ""; } } - - @Override public void deleteFileValue(String submodelId, String idShortPath) { @@ -302,5 +301,18 @@ private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { if (!smId.equals(newSubmodelId)) throw new IdentificationMismatchException(); } + + private String getTemporaryDirectoryPath() { + + String tempDirectoryPath = ""; + + try { + tempDirectoryPath = Files.createTempDirectory("basyx-temp").toAbsolutePath().toString(); + } catch (IOException e) { + e.printStackTrace(); + } + + return tempDirectoryPath; + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml index afc17f693..2d38ea0cd 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml @@ -37,5 +37,9 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-core + + org.apache.tika + tika-core + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 4fda2f6ea..976da66ab 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -24,16 +24,29 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; +import org.apache.commons.io.FileUtils; +import org.apache.tika.mime.MimeType; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; +import org.apache.tika.utils.StringUtils; +import org.bson.types.ObjectId; import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; import org.springframework.data.domain.Sort.Direction; @@ -41,7 +54,8 @@ import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; - +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.result.DeleteResult; /** @@ -51,12 +65,17 @@ * */ public class MongoDBSubmodelRepository implements SubmodelRepository { - private static String ID_JSON_PATH = "id"; + private static final String MONGO_ID = "_id"; + private static final String TEMP_DIR_PREFIX = "basyx-temp"; + private static final String GRIDFS_ID_DELIMITER = "#"; + private static final String ID_JSON_PATH = "id"; private MongoTemplate mongoTemplate; private String collectionName; private SubmodelServiceFactory submodelServiceFactory; + private GridFsTemplate gridFsTemplate; + /** * Creates the MongoDBSubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and uses a @@ -66,11 +85,25 @@ public class MongoDBSubmodelRepository implements SubmodelRepository { * @param collectionName * @param submodelServiceFactory */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, + SubmodelServiceFactory submodelServiceFactory) { this.mongoTemplate = mongoTemplate; this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; configureIndexForSubmodelId(mongoTemplate); + + configureDefaultGridFsTemplate(this.mongoTemplate); + } + + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, + SubmodelServiceFactory submodelServiceFactory, GridFsTemplate gridFsTemplate) { + this(mongoTemplate, collectionName, submodelServiceFactory); + this.gridFsTemplate = gridFsTemplate; + + if (this.gridFsTemplate == null) + configureDefaultGridFsTemplate(mongoTemplate); + + configureIndexForSubmodelId(mongoTemplate); } /** @@ -82,9 +115,22 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * @param submodelServiceFactory * @param submodels */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, Collection submodels) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, + SubmodelServiceFactory submodelServiceFactory, Collection submodels) { this(mongoTemplate, collectionName, submodelServiceFactory); initializeRemoteCollection(submodels); + + configureDefaultGridFsTemplate(this.mongoTemplate); + } + + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, + SubmodelServiceFactory submodelServiceFactory, Collection submodels, + GridFsTemplate gridFsTemplate) { + this(mongoTemplate, collectionName, submodelServiceFactory, submodels); + this.gridFsTemplate = gridFsTemplate; + + if (this.gridFsTemplate == null) + configureDefaultGridFsTemplate(mongoTemplate); } private void initializeRemoteCollection(Collection submodels) { @@ -96,8 +142,7 @@ private void initializeRemoteCollection(Collection submodels) { private void configureIndexForSubmodelId(MongoTemplate mongoTemplate) { Index idIndex = new Index().on(ID_JSON_PATH, Direction.ASC); - mongoTemplate.indexOps(Submodel.class) - .ensureIndex(idIndex); + mongoTemplate.indexOps(Submodel.class).ensureIndex(idIndex); } @Override @@ -107,8 +152,8 @@ public Collection getAllSubmodels() { @Override public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistException { - Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH) - .is(submodelId)), Submodel.class, collectionName); + Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), + Submodel.class, collectionName); if (submodel == null) { throw new ElementDoesNotExistException(submodelId); } @@ -117,8 +162,7 @@ public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistExcepti @Override public void updateSubmodel(String submodelId, Submodel submodel) throws ElementDoesNotExistException { - Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH) - .is(submodelId)); + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); throwIfSubmodelDoesNotExist(query, submodelId); throwIfMismatchingIds(submodelId, submodel); @@ -148,8 +192,8 @@ public void createSubmodel(Submodel submodel) throws CollidingIdentifierExceptio } private void throwIfCollidesWithRemoteId(Submodel submodel) { - if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH) - .is(submodel.getId())), Submodel.class, collectionName)) { + if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodel.getId())), + Submodel.class, collectionName)) { throw new CollidingIdentifierException(submodel.getId()); } } @@ -164,17 +208,20 @@ public Collection getSubmodelElements(String submodelId) throws } @Override - public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { + public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) + throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElement(submodelElementIdShort); } @Override - public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { + public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) + throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElementValue(submodelElementIdShort); } @Override - public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { + public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) + throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.setSubmodelElementValue(submodelElementIdShort, value); @@ -183,8 +230,8 @@ public void setSubmodelElementValue(String submodelId, String submodelElementIdS @Override public void deleteSubmodel(String submodelId) throws ElementDoesNotExistException { - DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH) - .is(submodelId)), Submodel.class, collectionName); + DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), + Submodel.class, collectionName); if (result.getDeletedCount() == 0) { throw new ElementDoesNotExistException(submodelId); @@ -201,7 +248,8 @@ public void createSubmodelElement(String submodelId, SubmodelElement submodelEle } @Override - public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) + throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.createSubmodelElement(idShortPath, submodelElement); @@ -230,20 +278,151 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { - // TODO Auto-generated method stub - return null; + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + throwIfFileDoesNotExist(fileSmElement); + + String fileId = getFileId(fileSmElement.getValue()); + + GridFSFile file = gridFsTemplate.findOne(new Query(Criteria.where(MONGO_ID).is(fileId))); + + InputStream fileIs = getGridFsFileAsInputStream(file); + + return createFileInTempDirectory(idShortPath, fileSmElement, fileIs); } @Override - public void setFileValue(String submodelId, String idShortPath, java.io.File file) { - // TODO Auto-generated method stub - + public void deleteFileValue(String submodelId, String idShortPath) { + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + throwIfFileDoesNotExist(fileSmElement); + + String fileId = getFileId(fileSmElement.getValue()); + + gridFsTemplate.delete(new Query(Criteria.where(MONGO_ID).is(fileId))); + + FileBlobValue fileValue = new FileBlobValue(StringUtils.EMPTY, StringUtils.EMPTY); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); } @Override - public void deleteFileValue(String identifier, String idShortPath) { - // TODO Auto-generated method stub - + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) + throws ElementDoesNotExistException, IdentificationMismatchException { + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + ObjectId id = gridFsTemplate.store(inputStream, fileSmElement.getValue(), fileSmElement.getContentType()); + + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), + appendFsIdToFileValue(fileSmElement, id)); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + private void configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { + this.gridFsTemplate = new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); + } + + private String appendFsIdToFileValue(File fileSmElement, ObjectId id) { + return id.toString() + GRIDFS_ID_DELIMITER + fileSmElement.getValue(); + } + + private java.io.File createFileInTempDirectory(String idShortPath, File fileSmElement, InputStream fileIs) { + + Path tempDir = createTempDirectory(TEMP_DIR_PREFIX); + + String absolutePath = tempDir.toAbsolutePath().toString(); + + String filePath = getFilePath(absolutePath, idShortPath, fileSmElement.getContentType()); + + java.io.File targetFile = new java.io.File(filePath); + + try { + FileUtils.copyInputStreamToFile(fileIs, targetFile); + } catch (IOException e) { + e.printStackTrace(); + } + + return targetFile; + } + + private Path createTempDirectory(String prefix) { + Path tempDir = null; + try { + tempDir = Files.createTempDirectory(prefix); + } catch (IOException e) { + e.printStackTrace(); + } + return tempDir; + } + + private InputStream getGridFsFileAsInputStream(GridFSFile file) { + InputStream fileIs = null; + + try { + fileIs = gridFsTemplate.getResource(file).getInputStream(); + } catch (IllegalStateException | IOException e1) { + e1.printStackTrace(); + } + return fileIs; + } + + private String getFileId(String value) { + return value.substring(0, value.indexOf(GRIDFS_ID_DELIMITER)); + } + + private void throwIfFileDoesNotExist(File fileSmElement) { + if (fileSmElement.getValue().isBlank() || fileSmElement.getValue().indexOf(GRIDFS_ID_DELIMITER) == -1) + throw new FileDoesNotExistException(fileSmElement.getIdShort()); + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + + if (!(submodelElement instanceof File)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + + private String getFilePath(String tmpDirectory, String idShortPath, String contentType) { + String fileName = idShortPath.replace("/", "-"); + + String extension = getFileExtension(contentType); + + return tmpDirectory + "/" + fileName + extension; + } + + private String getFileExtension(String contentType) { + MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); + try { + MimeType mimeType = allTypes.forName(contentType); + return mimeType.getExtension(); + } catch (MimeTypeException e) { + e.printStackTrace(); + return ""; + } } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index 8ff561fee..98a40f2bb 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.stereotype.Component; /** @@ -48,6 +49,7 @@ public class MongoDBSubmodelRepositoryFactory implements SubmodelRepositoryFacto private String collectionName; private SubmodelServiceFactory submodelServiceFactory; private Collection submodels; + private GridFsTemplate gridFsTemplate; @Autowired(required = false) public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory) { @@ -55,6 +57,12 @@ public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${b this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; } + + @Autowired(required = false) + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, GridFsTemplate gridFsTemplate) { + this(mongoTemplate, collectionName, submodelServiceFactory); + this.gridFsTemplate = gridFsTemplate; + } @Autowired(required = false) @@ -63,13 +71,21 @@ public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${b this(mongoTemplate, collectionName, submodelServiceFactory); this.submodels = submodels; } + + @Autowired(required = false) + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + Collection submodels, GridFsTemplate gridFsTemplate) { + this(mongoTemplate, collectionName, submodelServiceFactory); + this.submodels = submodels; + this.gridFsTemplate = gridFsTemplate; + } @Override public SubmodelRepository create() { if (this.submodels == null || this.submodels.isEmpty()) { - return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory); + return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, gridFsTemplate); } - return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, submodels); + return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, submodels, gridFsTemplate); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java index 80eb79933..25f2efb41 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java @@ -31,6 +31,7 @@ import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -40,20 +41,21 @@ public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); + private final GridFsTemplate GRIDFS_TEMPLATE = new GridFsTemplate(TEMPLATE.getMongoDatabaseFactory(), TEMPLATE.getConverter(), "TestSMEFiles"); private final InMemorySubmodelServiceFactory SUBMODEL_SERVICE_FACTORY = new InMemorySubmodelServiceFactory(); @Override protected SubmodelRepository getSubmodelRepository() { MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY).create(); + return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, GRIDFS_TEMPLATE).create(); } @Override protected SubmodelRepository getSubmodelRepository(Collection submodels) { MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels).create(); + return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels, GRIDFS_TEMPLATE).create(); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json new file mode 100644 index 000000000..f1ce92ea3 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json @@ -0,0 +1,5 @@ +{ + "name": "SampleJsonFile", + "description": "A JSON file for verification", + "version": 123 +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml index a704f5c1c..1fa44a1ee 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml @@ -25,10 +25,14 @@ tests test - org.eclipse.digitaltwin.aas4j model + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 788994fe1..940c3a47e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -29,8 +29,15 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.Collection; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXSD; import org.eclipse.digitaltwin.aas4j.v3.model.Property; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -39,11 +46,15 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; import org.eclipse.digitaltwin.basyx.submodelservice.value.PropertyValue; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; /** * Testsuite for implementations of the SubmodelRepository interface @@ -232,6 +243,58 @@ public void setPropertyValue() { assertEquals(expected, retrievedValue.getValue()); } + + @Test + public void getFile() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + String expectedFileExtension = "json"; + + InputStream expectedFile = getInputStreamOfFileFromClasspath("SampleJsonFile.json"); + + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + + File retrievedValue = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + assertEquals(expectedFileExtension, getExtension(retrievedValue.getName())); + + assertTrue(IOUtils.contentEquals(expectedFile, FileUtils.openInputStream(retrievedValue))); + } + + @Test(expected = FileDoesNotExistException.class) + public void getNonExistingFile() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } + + @Test(expected = ElementNotAFileException.class) + public void getFileFromNonFileSME() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + } + + @Test + public void deleteFile() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + try { + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + fail(); + } catch (Exception e) { + } + } + + @Test(expected = FileDoesNotExistException.class) + public void deleteNonExistingFile() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } @Test(expected = ElementDoesNotExistException.class) public void setNonExistingSubmodelElementValue() { @@ -369,5 +432,15 @@ private void assertIsEmpty(Collection submodels) { private String generateIdShortPath() { return DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ELEMENT_COLLECTION_ID_SHORT + "." + DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ELEMENT_LIST_ID_SHORT + "[0]"; } + + private InputStream getInputStreamOfFileFromClasspath(String fileName) throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource(fileName); + + return classPathResource.getInputStream(); + } + + private String getExtension(String filename) { + return FilenameUtils.getExtension(filename); + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 8636c5e0f..268657617 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -44,6 +44,7 @@ import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; From 5f5f222fddcf05980102ef4d85656857b2256031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:32:17 +0200 Subject: [PATCH 04/18] Add In-Memory backend und unit Test. Add Rest API for file upload Created unit test for file upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../InMemorySubmodelRepository.java | 176 +++++++++--------- .../TestInMemorySubmodelRepository.java | 9 +- .../SubmodelRepository.java | 6 +- .../core/SubmodelRepositorySuite.java | 12 ++ .../feature/mqtt/MqttSubmodelRepository.java | 17 +- .../SubmodelRepositoryApiHTTPController.java | 45 +++-- .../http/SubmodelRepositoryHTTPApi.java | 46 +++-- ...positorySubmodelElementsTestSuiteHTTP.java | 52 ++++++ ...bmodelRepositorySubmodelHTTPTestSuite.java | 48 ----- 9 files changed, 225 insertions(+), 186 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 8fcff4962..266d74346 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -37,22 +39,21 @@ import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; -import org.apache.tika.mime.MimeType; -import org.apache.tika.mime.MimeTypeException; -import org.apache.tika.mime.MimeTypes; +import org.apache.tika.utils.StringUtils; import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultFile; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; - /** * In-memory implementation of the SubmodelRepository * @@ -93,10 +94,7 @@ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, private void throwIfHasCollidingIds(Collection submodelsToCheck) { Set ids = new HashSet<>(); - submodelsToCheck.stream() - .map(submodel -> submodel.getId()) - .filter(id -> !ids.add(id)) - .findAny() + submodelsToCheck.stream().map(submodel -> submodel.getId()).filter(id -> !ids.add(id)).findAny() .ifPresent(id -> { throw new CollidingIdentifierException(id); }); @@ -105,24 +103,20 @@ private void throwIfHasCollidingIds(Collection submodelsToCheck) { private Map createServices(Collection submodels) { Map map = new LinkedHashMap<>(); submodels.forEach(submodel -> map.put(submodel.getId(), submodelServiceFactory.create(submodel))); - + return map; } @Override public Collection getAllSubmodels() { - return submodelServices.values() - .stream() - .map(service -> service.getSubmodel()) - .collect(Collectors.toList()); + return submodelServices.values().stream().map(service -> service.getSubmodel()).collect(Collectors.toList()); } @Override public Submodel getSubmodel(String id) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(id); - return submodelServices.get(id) - .getSubmodel(); + return submodelServices.get(id).getSubmodel(); } @Override @@ -155,32 +149,31 @@ private void throwIfSubmodelDoesNotExist(String id) { public Collection getSubmodelElements(String submodelId) { throwIfSubmodelDoesNotExist(submodelId); - return submodelServices.get(submodelId) - .getSubmodelElements(); + return submodelServices.get(submodelId).getSubmodelElements(); } @Override - public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) + throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); - return submodelServices.get(submodelId) - .getSubmodelElement(smeIdShort); + return submodelServices.get(submodelId).getSubmodelElement(smeIdShort); } @Override - public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) + throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); - return submodelServices.get(submodelId) - .getSubmodelElementValue(smeIdShort); + return submodelServices.get(submodelId).getSubmodelElementValue(smeIdShort); } @Override - public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { + public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) + throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); - submodelServices.get(submodelId) - .setSubmodelElementValue(smeIdShort, value); + submodelServices.get(submodelId).setSubmodelElementValue(smeIdShort, value); } @Override @@ -194,24 +187,22 @@ public void deleteSubmodel(String submodelId) throws ElementDoesNotExistExceptio public void createSubmodelElement(String submodelId, SubmodelElement smElement) { throwIfSubmodelDoesNotExist(submodelId); - submodelServices.get(submodelId) - .createSubmodelElement(smElement); + submodelServices.get(submodelId).createSubmodelElement(smElement); } @Override - public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException { + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) + throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); - submodelServices.get(submodelId) - .createSubmodelElement(idShortPath, smElement); + submodelServices.get(submodelId).createSubmodelElement(idShortPath, smElement); } @Override public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); - submodelServices.get(submodelId) - .deleteSubmodelElement(idShortPath); + submodelServices.get(submodelId).deleteSubmodelElement(idShortPath); } @Override @@ -226,74 +217,71 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { return submodel; } - @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { - throwIfSubmodelDoesNotExist(submodelId); + throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - - return new java.io.File(((File)submodelElement).getValue()); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + String filePath = fileSmElement.getValue(); + + throwIfFileDoesNotExist(fileSmElement, filePath); + + return new java.io.File(filePath); + } @Override - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream ) throws IdentificationMismatchException{ + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException { throwIfSubmodelDoesNotExist(submodelId); - + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - if(submodelElement.getCategory().equals("File")) - throw new Exception(); - - File fileElement = new DefaultFile(); - String filePath = getFilePath(idShortPath, fileElement); + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + String filePath = getFilePath(submodelId, idShortPath, fileSmElement); java.io.File targetFile = new java.io.File(filePath); - + try (FileOutputStream outStream = new FileOutputStream(targetFile)) { - IOUtils.copy(inputStream, outStream); - } + IOUtils.copy(inputStream, outStream); + } + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); } - -// @SuppressWarnings("unchecked") -// private void createFile(String idShortPath, Object newValue, ISubmodelElement submodelElement) throws IOException { -// File file = File.createAsFacade((Map) submodelElement); -// String filePath = getFilePath(idShortPath, file); -// -// java.io.File targetFile = new java.io.File(filePath); -// -// try (FileOutputStream outStream = new FileOutputStream(targetFile); -// InputStream inStream = (InputStream) newValue) { -// IOUtils.copy(inStream, outStream); -// } -// } -// - private String getFilePath(String idShortPath, File file) { - String fileName = idShortPath.replaceAll("/", "-"); - - String extension = getFileExtension(file); - - return tmpDirectory + "/" + fileName + extension; + + private String getFilePath(String submodelId, String idShortPath, File file) { + String fileName = submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + file.getValue(); + + return tmpDirectory + "/" + fileName; } - private String getFileExtension(File file) { - MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); - try { - MimeType mimeType = allTypes.forName(file.getContentType()); - return mimeType.getExtension(); - } catch (MimeTypeException e) { - e.printStackTrace(); - return ""; - } + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + if (!(submodelElement instanceof File)) + throw new ElementNotAFileException(submodelElement.getIdShort()); } @Override public void deleteFileValue(String submodelId, String idShortPath) { - throwIfSubmodelDoesNotExist(submodelId); + throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - - java.io.File tmpFile = new java.io.File(((File)submodelElement).getValue()); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSubmodelElement = (File) submodelElement; + String filePath = fileSubmodelElement.getValue(); + + throwIfFileDoesNotExist(fileSubmodelElement, filePath); + + java.io.File tmpFile = new java.io.File(filePath); tmpFile.delete(); - - ((File)submodelElement).setValue(""); - } + + FileBlobValue fileValue = new FileBlobValue(StringUtils.EMPTY, StringUtils.EMPTY); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { String newSubmodelId = newSubmodel.getId(); @@ -301,17 +289,31 @@ private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { if (!smId.equals(newSubmodelId)) throw new IdentificationMismatchException(); } - + + private void throwIfFileDoesNotExist(File fileSmElement, String filePath) { + if (fileSmElement.getValue().isBlank() || !isFilePathValid(filePath)) + throw new FileDoesNotExistException(fileSmElement.getIdShort()); + } + + private boolean isFilePathValid(String filePath) { + try { + Paths.get(filePath); + } catch (InvalidPathException | NullPointerException ex) { + return false; + } + return true; + } + private String getTemporaryDirectoryPath() { - + String tempDirectoryPath = ""; - + try { tempDirectoryPath = Files.createTempDirectory("basyx-temp").toAbsolutePath().toString(); } catch (IOException e) { e.printStackTrace(); } - + return tempDirectoryPath; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java index 79f092913..a152764e3 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java @@ -52,7 +52,7 @@ protected SubmodelRepository getSubmodelRepository() { protected SubmodelRepository getSubmodelRepository(Collection submodels) { return new InMemorySubmodelRepository(new InMemorySubmodelServiceFactory(), submodels); } - + @Test(expected = CollidingIdentifierException.class) public void idCollisionDuringConstruction() { Collection submodelsWithCollidingIds = createSubmodelCollectionWithCollidingIds(); @@ -66,10 +66,13 @@ public void assertIdUniqueness() { } private Collection createSubmodelCollectionWithCollidingIds() { - return Arrays.asList(DummySubmodelFactory.createTechnicalDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); + return Arrays.asList(DummySubmodelFactory.createTechnicalDataSubmodel(), + DummySubmodelFactory.createTechnicalDataSubmodel()); } private Collection createSubmodelCollectionWithUniqueIds() { - return Arrays.asList(DummySubmodelFactory.createSimpleDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); + return Arrays.asList(DummySubmodelFactory.createSimpleDataSubmodel(), + DummySubmodelFactory.createTechnicalDataSubmodel()); } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index ebff2b086..fedc9d0ff 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -25,15 +25,14 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.IOException; import java.io.InputStream; import java.util.Collection; -import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; @@ -214,8 +213,9 @@ public default String getName() { * the file object to upload * @return * @throws ElementDoesNotExistException + * @throws IOException */ - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws ElementDoesNotExistException,IdentificationMismatchException; + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException; /** * Deletes the file of a file submodelelement diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 940c3a47e..0192def13 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -263,10 +263,21 @@ public void getFile() throws IOException { @Test(expected = FileDoesNotExistException.class) public void getNonExistingFile() throws IOException { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + deleteFileIfExisted(repo); repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); } + private void deleteFileIfExisted(SubmodelRepository repo) { + try { + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + }catch(FileDoesNotExistException e) { + return; + } + + } + @Test(expected = ElementNotAFileException.class) public void getFileFromNonFileSME() throws IOException { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); @@ -292,6 +303,7 @@ public void deleteFile() throws IOException { @Test(expected = FileDoesNotExistException.class) public void deleteNonExistingFile() throws IOException { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + deleteFileIfExisted(repo); repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index 3a28adba8..7b2091a10 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -1,14 +1,16 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; +import java.io.IOException; +import java.io.InputStream; import java.util.Collection; -import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelElementSerializer; import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelSerializer; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; @@ -180,20 +182,17 @@ private MqttMessage createMqttMessage(String payload) { @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { - // TODO Auto-generated method stub - return null; + throw new FeatureNotImplementedException(); } @Override - public void setFileValue(String submodelId, String idShortPath, java.io.File file) { - // TODO Auto-generated method stub - + public void deleteFileValue(String identifier, String idShortPath) { + throw new FeatureNotImplementedException(); } @Override - public void deleteFileValue(String identifier, String idShortPath) { - // TODO Auto-generated method stub - + public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException { + throw new FeatureNotImplementedException(); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 268657617..7db51dd66 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -25,6 +25,8 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -32,9 +34,11 @@ import javax.validation.constraints.Min; import javax.validation.constraints.Size; -import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResultPagingMetadata; @@ -44,6 +48,7 @@ import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -158,29 +163,45 @@ public ResponseEntity getSubmodelByIdMetadata(Base64UrlEncodedIdentifi } @Override - public ResponseEntity GetFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { - java.io.File value = repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath); - return new ResponseEntity(value, HttpStatus.OK); - } + public ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier,@Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath) { + try { + FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier, idShortPath)); + Resource resource = new InputStreamResource(fileInputStream); + return new ResponseEntity(resource, HttpStatus.ACCEPTED); + }catch(FileNotFoundException | FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + }catch(ElementNotAFileException e) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + } @Override public ResponseEntity putFileByPath(String submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { - java.io.File destinationFile = new java.io.File("temp"); try { - file.transferTo(destinationFile); - repository.setFileValue(submodelIdentifier, idShortPath, destinationFile); - return new ResponseEntity(HttpStatus.NO_CONTENT); - } catch (IllegalStateException | IOException e) { + repository.setFileValue(submodelIdentifier, idShortPath, file.getInputStream()); + return new ResponseEntity(HttpStatus.OK); + } catch (FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + }catch(ElementNotAFileException e) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + }catch(IOException e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @Override public ResponseEntity deleteFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { - repository.deleteFileValue(submodelIdentifier.getIdentifier(), idShortPath); - return new ResponseEntity(HttpStatus.OK); + try{ + repository.deleteFileValue(submodelIdentifier.getIdentifier(), idShortPath); + return new ResponseEntity(HttpStatus.OK); + } catch (FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + }catch(ElementNotAFileException e) { + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } } + private ResponseEntity handleSubmodelElementValueSetRequest(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, SubmodelElementValue body) { repository.setSubmodelElementValue(submodelIdentifier.getIdentifier(), idShortPath, body); return new ResponseEntity(HttpStatus.OK); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index 9e5f058aa..70857e529 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -36,7 +36,6 @@ import javax.validation.constraints.Min; import javax.validation.constraints.Size; -import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; @@ -44,6 +43,7 @@ import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; @@ -372,30 +372,28 @@ ResponseEntity postSubmodelElementSubmodelRepo( ResponseEntity deleteSubmodelElementByPathSubmodelRepo( @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); - -//***********************************// + @Operation(summary = "Downloads file content from a specific submodel element from the Submodel at a specified path", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested file", content = @Content(mediaType = "application/octet-stream", schema = @Schema(implementation = Resource.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/octet-stream", "application/json" }, + method = RequestMethod.GET) + ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); - @Operation(summary = "Returns a specific file from the Submodel at a specified path", description = "", tags = { "Submodel Repository API" }) - @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Requested file", content = @Content(mediaType = "application/octet-stream", schema = @Schema(implementation = SubmodelElement.class))), - - @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) - @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/GetFileByPath", produces = { "application/octet-stream" }, method = RequestMethod.GET) - ResponseEntity GetFileByPathSubmodelRepo( - @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, - @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); - - @Operation(summary = "Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) + @Operation(summary = "Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "Submodel element updated successfully"), @@ -410,7 +408,7 @@ ResponseEntity GetFileByPathSubmodelRepo( @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) - @RequestMapping(value = "/aas/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", produces = { "application/json" }, consumes = { "multipart/form-data" }, method = RequestMethod.PUT) diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java index 38917638a..9d208bf0f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java @@ -29,12 +29,22 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; @@ -42,6 +52,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; /** * Base testsuite for all Submodel Repository HTTP tests related to a specific @@ -202,6 +213,47 @@ public void setFileValue() throws IOException, ParseException { BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } + + @Test + public void uploadFileToFileSubmodelElement() throws IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("8A6344BDAB57E184","FileData"); + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); + + } + + @Test + public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("8A6344BDAB57E184","NonFileParameter"); + assertEquals(HttpStatus.BAD_REQUEST.value(), submodelElementFileUploadResponse.getCode()); + } + + + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + java.io.File file = ResourceUtils.getFile("src/test/resources/BaSyx-Logo.png"); + + HttpPut putRequest = new HttpPut(createSubmodelElementsURL(submodelId, submodelElementIdShort)); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + putRequest.setEntity(multipart); + + return client.execute(putRequest); + } + + private String createSubmodelElementsURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/"+ submodelElementIdShort+ "/attachment"; + } + @Test public void getBlobValue() throws IOException, ParseException { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index 64377b7fe..274b48c31 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -27,21 +27,11 @@ import static org.junit.Assert.assertEquals; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; -import org.apache.hc.client5.http.ClientProtocolException; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.entity.mime.FileBody; -import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; @@ -51,7 +41,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; -import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -176,44 +165,7 @@ public void deleteNonExistingSubmodel() throws IOException { assertEquals(HttpStatus.NOT_FOUND.value(), deletionResponse.getCode()); } - @Test - public void uploadFileToNonFileSubmodelElement() - throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("NonFileParameter"); - assertEquals(HttpStatus.BAD_REQUEST.value(), submodelElementFileUploadResponse.getCode()); - } - @Test - public void uploadFileToFileSubmodelElement() throws IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("FileData"); - assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); - - } - - private CloseableHttpResponse uploadFileToSubmodelElement(String submodelElementIdShort) throws IOException { - CloseableHttpClient client = HttpClients.createDefault(); - - java.io.File file = ResourceUtils.getFile("src/test/resources/BaSyx-Logo.png"); - - HttpPost uploadFile = new HttpPost( - getURL() + "/basyx.examples.test/aas/submodels/FileTests/submodel/submodelElements/" - + submodelElementIdShort + "/PutFileByPath"); - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - - builder.addPart("file", new FileBody(file)); - builder.setContentType(ContentType.MULTIPART_FORM_DATA); - - HttpEntity multipart = builder.build(); - uploadFile.setEntity(multipart); - - return client.execute(uploadFile); - } - - private void assertSubmodelCreationReponse(String submodelJSON, CloseableHttpResponse creationResponse) throws IOException, ParseException, JsonProcessingException, JsonMappingException { assertEquals(HttpStatus.CREATED.value(), creationResponse.getCode()); String response = BaSyxHttpTestUtils.getResponseAsString(creationResponse); From 371573ff36e26f700ff080baf4364dca21e09505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:42:49 +0200 Subject: [PATCH 05/18] Add new Exception for mqtt Add json file for test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../core/exceptions/FeatureNotImplementedException.java | 5 +++++ .../src/test/resources/SampleJsonFile.json | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java create mode 100644 basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java new file mode 100644 index 000000000..4cfce38ee --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java @@ -0,0 +1,5 @@ +package org.eclipse.digitaltwin.basyx.core.exceptions; + +@SuppressWarnings("serial") +public class FeatureNotImplementedException extends RuntimeException { +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json new file mode 100644 index 000000000..f1ce92ea3 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json @@ -0,0 +1,5 @@ +{ + "name": "SampleJsonFile", + "description": "A JSON file for verification", + "version": 123 +} From 32d6eed5cbe498de5463b0cef1768d7343530289 Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish Date: Fri, 22 Sep 2023 13:00:43 +0200 Subject: [PATCH 06/18] Fixes the File upload issue with HTTP Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../InMemorySubmodelRepository.java | 3 ++ .../MongoDBSubmodelRepository.java | 4 ++- .../SubmodelRepositoryApiHTTPController.java | 8 ++--- .../http/SubmodelRepositoryHTTPApi.java | 4 +-- ...positorySubmodelElementsTestSuiteHTTP.java | 36 ++++++++++++++----- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 266d74346..fc5773128 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -247,9 +247,12 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu try (FileOutputStream outStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outStream); } + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); setSubmodelElementValue(submodelId, idShortPath, fileValue); + + inputStream.close(); } private String getFilePath(String submodelId, String idShortPath, File file) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 976da66ab..2f41625d2 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -324,7 +324,7 @@ public void deleteFileValue(String submodelId, String idShortPath) { @Override public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) - throws ElementDoesNotExistException, IdentificationMismatchException { + throws ElementDoesNotExistException, IdentificationMismatchException, IOException { Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); throwIfSubmodelDoesNotExist(query, submodelId); @@ -341,6 +341,8 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu appendFsIdToFileValue(fileSmElement, id)); setSubmodelElementValue(submodelId, idShortPath, fileValue); + + inputStream.close(); } private void configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 7db51dd66..c214a7a18 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -163,9 +163,9 @@ public ResponseEntity getSubmodelByIdMetadata(Base64UrlEncodedIdentifi } @Override - public ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier,@Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath) { + public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { try { - FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier, idShortPath)); + FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath)); Resource resource = new InputStreamResource(fileInputStream); return new ResponseEntity(resource, HttpStatus.ACCEPTED); }catch(FileNotFoundException | FileDoesNotExistException | ElementDoesNotExistException e) { @@ -176,10 +176,10 @@ public ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, } @Override - public ResponseEntity putFileByPath(String submodelIdentifier, String idShortPath, String fileName, + public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { try { - repository.setFileValue(submodelIdentifier, idShortPath, file.getInputStream()); + repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file.getInputStream()); return new ResponseEntity(HttpStatus.OK); } catch (FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index 70857e529..35c05ab2c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -391,7 +391,7 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", produces = { "application/octet-stream", "application/json" }, method = RequestMethod.GET) - ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); + ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); @Operation(summary = "Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) @ApiResponses(value = { @@ -412,7 +412,7 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( produces = { "application/json" }, consumes = { "multipart/form-data" }, method = RequestMethod.PUT) - ResponseEntity putFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath, @Parameter(in = ParameterIn.DEFAULT, description = "", required=true,schema=@Schema()) @RequestParam(value="fileName", required=true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); + ResponseEntity putFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath, @Parameter(in = ParameterIn.DEFAULT, description = "", required=true,schema=@Schema()) @RequestParam(value="fileName", required=true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); @Operation(summary = "Deletes the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "File deleted successfully"), diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java index 9d208bf0f..e0c281e36 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java @@ -36,6 +36,7 @@ import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -43,6 +44,7 @@ import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; @@ -217,10 +219,12 @@ public void setFileValue() throws IOException, ParseException { @Test public void uploadFileToFileSubmodelElement() throws IOException { String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("8A6344BDAB57E184","FileData"); + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); - } @Test @@ -234,10 +238,28 @@ public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, U private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { CloseableHttpClient client = HttpClients.createDefault(); + + String fileName = "BaSyx-Logo.png"; - java.io.File file = ResourceUtils.getFile("src/test/resources/BaSyx-Logo.png"); + java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); - HttpPut putRequest = new HttpPut(createSubmodelElementsURL(submodelId, submodelElementIdShort)); + HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); + + return executePutRequest(client, putRequest); + } + + private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, + java.io.File file) { + HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -246,15 +268,13 @@ private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, Str HttpEntity multipart = builder.build(); putRequest.setEntity(multipart); - - return client.execute(putRequest); + return putRequest; } - private String createSubmodelElementsURL(String submodelId, String submodelElementIdShort) { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/"+ submodelElementIdShort+ "/attachment"; + private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; } - @Test public void getBlobValue() throws IOException, ParseException { CloseableHttpResponse response = requestSubmodelElementValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); From ab9280c80ce3ce59060c4ec581aea21a9abf74bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:42:49 +0200 Subject: [PATCH 07/18] Add new Exception for mqtt Add json file for test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../http/SubmodelRepositoryHTTPApi.java | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index 35c05ab2c..a660512cf 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -414,23 +414,25 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( method = RequestMethod.PUT) ResponseEntity putFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath, @Parameter(in = ParameterIn.DEFAULT, description = "", required=true,schema=@Schema()) @RequestParam(value="fileName", required=true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); - @Operation(summary = "Deletes the file of an existing submodel element at a specified path within the submodel element hierarchy", description = "", tags = { "Submodel Repository API" }) - @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "File deleted successfully"), - - @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), - - @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + @Operation(summary = "Deletes file content of an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Submodel element updated successfully"), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/json" }, + method = RequestMethod.DELETE) + ResponseEntity deleteFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); - @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) - @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/DeleteFileByPath", produces = { "application/json" }, method = RequestMethod.DELETE) - ResponseEntity deleteFileByPathSubmodelRepo( - @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, - @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); } From d7992a8d05a6cba04dc6f919a57df241948db280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:28:37 +0200 Subject: [PATCH 08/18] Add http unit test for get- set- delete files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> Co-authored-by: Danish, Mohammad Ghazanfar Ali --- .../InMemorySubmodelRepository.java | 32 +++--- .../MongoDBSubmodelRepository.java | 9 +- .../SubmodelRepositoryApiHTTPController.java | 29 +++--- .../http/SubmodelRepositoryHTTPApi.java | 2 +- ...positorySubmodelElementsTestSuiteHTTP.java | 97 ++++++++++++++----- .../InMemorySubmodelService.java | 2 + .../submodelservice/DummySubmodelFactory.java | 5 + 7 files changed, 120 insertions(+), 56 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index fc5773128..595555b90 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -94,10 +94,9 @@ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, private void throwIfHasCollidingIds(Collection submodelsToCheck) { Set ids = new HashSet<>(); - submodelsToCheck.stream().map(submodel -> submodel.getId()).filter(id -> !ids.add(id)).findAny() - .ifPresent(id -> { - throw new CollidingIdentifierException(id); - }); + submodelsToCheck.stream().map(submodel -> submodel.getId()).filter(id -> !ids.add(id)).findAny().ifPresent(id -> { + throw new CollidingIdentifierException(id); + }); } private Map createServices(Collection submodels) { @@ -153,24 +152,21 @@ public Collection getSubmodelElements(String submodelId) { } @Override - public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) - throws ElementDoesNotExistException { + public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); return submodelServices.get(submodelId).getSubmodelElement(smeIdShort); } @Override - public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) - throws ElementDoesNotExistException { + public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); return submodelServices.get(submodelId).getSubmodelElementValue(smeIdShort); } @Override - public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) - throws ElementDoesNotExistException { + public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); submodelServices.get(submodelId).setSubmodelElementValue(smeIdShort, value); @@ -191,8 +187,7 @@ public void createSubmodelElement(String submodelId, SubmodelElement smElement) } @Override - public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) - throws ElementDoesNotExistException { + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException { throwIfSubmodelDoesNotExist(submodelId); submodelServices.get(submodelId).createSubmodelElement(idShortPath, smElement); @@ -238,7 +233,7 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement); + throwIfSmElementIsNotAFile(submodelElement, inputStream); File fileSmElement = (File) submodelElement; String filePath = getFilePath(submodelId, idShortPath, fileSmElement); @@ -247,11 +242,11 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu try (FileOutputStream outStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outStream); } - + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); setSubmodelElementValue(submodelId, idShortPath, fileValue); - + inputStream.close(); } @@ -261,6 +256,13 @@ private String getFilePath(String submodelId, String idShortPath, File file) { return tmpDirectory + "/" + fileName; } + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { + if (!(submodelElement instanceof File)) { + inputStream.close(); + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + } + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { if (!(submodelElement instanceof File)) throw new ElementNotAFileException(submodelElement.getIdShort()); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 2f41625d2..b7305ae19 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -331,7 +331,7 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement); + throwIfSmElementIsNotAFile(submodelElement, inputStream); File fileSmElement = (File) submodelElement; @@ -407,6 +407,13 @@ private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { if (!(submodelElement instanceof File)) throw new ElementNotAFileException(submodelElement.getIdShort()); } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { + if (!(submodelElement instanceof File)) { + inputStream.close(); + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + } private String getFilePath(String tmpDirectory, String idShortPath, String contentType) { String fileName = idShortPath.replace("/", "-"); diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index c214a7a18..486843fdd 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -92,7 +92,6 @@ public ResponseEntity deleteSubmodelElementByPathSubmodelRepo( return new ResponseEntity(HttpStatus.NO_CONTENT); } - @Override public ResponseEntity getAllSubmodels(@Size(min = 1, max = 3072) @Valid Base64UrlEncodedIdentifier semanticId, @Valid String idShort, @Min(1) @Valid Integer limit, @Valid String cursor, @Valid String level, @Valid String extent) { @@ -117,7 +116,7 @@ public ResponseEntity putSubmodelById(Base64UrlEncodedIdentifier submodelI @Override public ResponseEntity getAllSubmodelElements(Base64UrlEncodedIdentifier submodelIdentifier, @Min(1) @Valid Integer limit, @Valid String cursor, @Valid String level, @Valid String extent) { Collection submodelElements = repository.getSubmodelElements(submodelIdentifier.getIdentifier()); - + GetSubmodelElementsResult paginatedSubmodelElement = new GetSubmodelElementsResult(); paginatedSubmodelElement.setResult(new ArrayList<>(submodelElements)); paginatedSubmodelElement.setPagingMetadata(new PagedResultPagingMetadata().cursor("nextSubmodelElementCursor")); @@ -127,7 +126,7 @@ public ResponseEntity getAllSubmodelElements(Base64UrlEncodedIdenti @Override public ResponseEntity getSubmodelElementByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, @Valid String level, @Valid String extent) { - return handleSubmodelElementValueNormalGetRequest(submodelIdentifier.getIdentifier(), idShortPath); + return handleSubmodelElementValueNormalGetRequest(submodelIdentifier.getIdentifier(), idShortPath); } @Override @@ -167,41 +166,40 @@ public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submode try { FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath)); Resource resource = new InputStreamResource(fileInputStream); - return new ResponseEntity(resource, HttpStatus.ACCEPTED); - }catch(FileNotFoundException | FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(resource, HttpStatus.OK); + } catch (FileNotFoundException | FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); - }catch(ElementNotAFileException e) { + } catch (ElementNotAFileException e) { return new ResponseEntity(HttpStatus.BAD_REQUEST); } - } + } @Override - public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, - @Valid MultipartFile file) { + public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { try { repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file.getInputStream()); return new ResponseEntity(HttpStatus.OK); } catch (FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); - }catch(ElementNotAFileException e) { + } catch (ElementNotAFileException e) { return new ResponseEntity(HttpStatus.BAD_REQUEST); - }catch(IOException e) { + } catch (IOException e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @Override - public ResponseEntity deleteFileByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { - try{ + public ResponseEntity deleteFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { + try { repository.deleteFileValue(submodelIdentifier.getIdentifier(), idShortPath); return new ResponseEntity(HttpStatus.OK); } catch (FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); - }catch(ElementNotAFileException e) { + } catch (ElementNotAFileException e) { return new ResponseEntity(HttpStatus.BAD_REQUEST); } } - + private ResponseEntity handleSubmodelElementValueSetRequest(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, SubmodelElementValue body) { repository.setSubmodelElementValue(submodelIdentifier.getIdentifier(), idShortPath, body); return new ResponseEntity(HttpStatus.OK); @@ -217,5 +215,4 @@ private ResponseEntity handleSubmodelElementValueNormalGetReque return new ResponseEntity(submodelElement, HttpStatus.OK); } - } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index a660512cf..194d35f0e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -432,7 +432,7 @@ ResponseEntity deleteSubmodelElementByPathSubmodelRepo( @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", produces = { "application/json" }, method = RequestMethod.DELETE) - ResponseEntity deleteFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") String submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); + ResponseEntity deleteFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java index e0c281e36..2e4c039c8 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java @@ -30,13 +30,15 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.entity.mime.FileBody; -import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -46,7 +48,6 @@ import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; @@ -215,52 +216,46 @@ public void setFileValue() throws IOException, ParseException { BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } - + @Test public void uploadFileToFileSubmodelElement() throws IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); - - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); - - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("8A6344BDAB57E184","FileData"); - + CloseableHttpResponse submodelElementFileUploadResponse = uploadFile(); + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); } - + @Test public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("8A6344BDAB57E184","NonFileParameter"); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); assertEquals(HttpStatus.BAD_REQUEST.value(), submodelElementFileUploadResponse.getCode()); } - - + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { CloseableHttpClient client = HttpClients.createDefault(); - + String fileName = "BaSyx-Logo.png"; java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); - + HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); - - return executePutRequest(client, putRequest); + + return executePutRequest(client, putRequest); } private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { CloseableHttpResponse response = client.execute(putRequest); - HttpEntity responseEntity = response.getEntity(); + HttpEntity responseEntity = response.getEntity(); - EntityUtils.consume(responseEntity); + EntityUtils.consume(responseEntity); return response; } - private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, - java.io.File file) { + private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); - + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addPart("file", new FileBody(file)); @@ -270,11 +265,67 @@ private HttpPut createPutRequestWithFile(String submodelId, String submodelEleme putRequest.setEntity(multipart); return putRequest; } - + private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; } + @Test + public void deleteFile() throws FileNotFoundException, IOException { + uploadFile(); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + uploadFile(); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getCode()); + } + + private CloseableHttpResponse uploadFile() throws FileNotFoundException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + return uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + } + + @Test + public void getFile() throws FileNotFoundException, IOException, ParseException { + uploadFile(); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + String received = BaSyxHttpTestUtils.getResponseAsString(response); + + String fileName = "BaSyx-Logo.png"; + assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); + + } + + static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + + @Test + public void getFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + uploadFile(); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getCode()); + } + + private String createSMEFileDeleteURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private String createSMEFileGetURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + @Test public void getBlobValue() throws IOException, ParseException { CloseableHttpResponse response = requestSubmodelElementValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java index 56c65ffa2..f893e65be 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java @@ -85,6 +85,7 @@ public SubmodelElementValue getSubmodelElementValue(String idShort) throws Eleme return submodelElementValueFactory.create(getSubmodelElement(idShort)).getValue(); } + @SuppressWarnings("unchecked") @Override public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); @@ -148,6 +149,7 @@ private void deleteNestedSubmodelElement(String idShortPath) { String collectionId = helper.extractDirectParentSubmodelElementListIdShort(idShortPath); SubmodelElementList list = (SubmodelElementList) parser.getSubmodelElementFromIdShortPath(collectionId); list.getValue().remove(sm); + return; } String collectionId = helper.extractDirectParentSubmodelElementCollectionIdShort(idShortPath); SubmodelElementCollection collection = (SubmodelElementCollection) parser.getSubmodelElementFromIdShortPath(collectionId); diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java index 8256b6372..dede0636a 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java @@ -92,6 +92,11 @@ public class DummySubmodelFactory { public static final String SUBMODEL_SIMPLE_DATA_ID_SHORT = "simpleSubmodel001"; public static final String SUBMODEL_SIMPLE_DATA_ID = "simpleSubmodel001"; public static final String SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT = "elementToDelete"; + + //SUBMODEL_FOR_FILE_TEST + public static final String SUBMODEL_FOR_FILE_TEST = "8A6344BDAB57E184"; + public static final String SUBMODEL_ELEMENT_FILE_ID_SHORT = "FileData"; + public static final String SUBMODEL_ELEMENT_NON_FILE_ID_SHORT = "NonFileParameter"; public static Collection getSubmodels() { return Arrays.asList(createTechnicalDataSubmodel(), createOperationalDataSubmodel(), createSimpleDataSubmodel()); From 85ca439f8a2b13d2794870df177f984b7b8edbeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:15:44 +0200 Subject: [PATCH 09/18] In SubmodelRepositoryApiHTTPController, Change Http status code to "precondition failed" if the submodel element is not a file sme. In InMemorySubmodelRepository, delete unused exception. Refactor Http Test --- .../InMemorySubmodelRepository.java | 39 +++--- .../SubmodelRepositoryApiHTTPController.java | 12 +- ...positorySubmodelElementsTestSuiteHTTP.java | 122 ++++++++++-------- ...ubmodelRepositorySubmodelElementsHTTP.java | 15 +++ 4 files changed, 105 insertions(+), 83 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 595555b90..f71a2c1ac 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -250,24 +250,6 @@ public void setFileValue(String submodelId, String idShortPath, InputStream inpu inputStream.close(); } - private String getFilePath(String submodelId, String idShortPath, File file) { - String fileName = submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + file.getValue(); - - return tmpDirectory + "/" + fileName; - } - - private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { - if (!(submodelElement instanceof File)) { - inputStream.close(); - throw new ElementNotAFileException(submodelElement.getIdShort()); - } - } - - private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { - if (!(submodelElement instanceof File)) - throw new ElementNotAFileException(submodelElement.getIdShort()); - } - @Override public void deleteFileValue(String submodelId, String idShortPath) { throwIfSubmodelDoesNotExist(submodelId); @@ -288,6 +270,24 @@ public void deleteFileValue(String submodelId, String idShortPath) { setSubmodelElementValue(submodelId, idShortPath, fileValue); } + private String getFilePath(String submodelId, String idShortPath, File file) { + String fileName = submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + file.getValue(); + + return tmpDirectory + "/" + fileName; + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { + if (!(submodelElement instanceof File)) { + inputStream.close(); + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + if (!(submodelElement instanceof File)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { String newSubmodelId = newSubmodel.getId(); @@ -310,15 +310,12 @@ private boolean isFilePathValid(String filePath) { } private String getTemporaryDirectoryPath() { - String tempDirectoryPath = ""; - try { tempDirectoryPath = Files.createTempDirectory("basyx-temp").toAbsolutePath().toString(); } catch (IOException e) { e.printStackTrace(); } - return tempDirectoryPath; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 486843fdd..9ad46d8ef 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -167,10 +167,12 @@ public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submode FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath)); Resource resource = new InputStreamResource(fileInputStream); return new ResponseEntity(resource, HttpStatus.OK); - } catch (FileNotFoundException | FileDoesNotExistException | ElementDoesNotExistException e) { + } catch (FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); } catch (ElementNotAFileException e) { - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); + } catch(FileNotFoundException e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -179,10 +181,10 @@ public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIde try { repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file.getInputStream()); return new ResponseEntity(HttpStatus.OK); - } catch (FileDoesNotExistException | ElementDoesNotExistException e) { + } catch (ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); } catch (ElementNotAFileException e) { - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); } catch (IOException e) { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } @@ -196,7 +198,7 @@ public ResponseEntity deleteFileByPath(Base64UrlEncodedIdentifier submodel } catch (FileDoesNotExistException | ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); } catch (ElementNotAFileException e) { - return new ResponseEntity(HttpStatus.BAD_REQUEST); + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java index 2e4c039c8..e8bf8f820 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelElementsTestSuiteHTTP.java @@ -219,60 +219,26 @@ public void setFileValue() throws IOException, ParseException { @Test public void uploadFileToFileSubmodelElement() throws IOException { - CloseableHttpResponse submodelElementFileUploadResponse = uploadFile(); + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); } @Test public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); - assertEquals(HttpStatus.BAD_REQUEST.value(), submodelElementFileUploadResponse.getCode()); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); } - private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { - CloseableHttpClient client = HttpClients.createDefault(); - - String fileName = "BaSyx-Logo.png"; - - java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); - - HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); - - return executePutRequest(client, putRequest); - } - - private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { - CloseableHttpResponse response = client.execute(putRequest); - - HttpEntity responseEntity = response.getEntity(); - - EntityUtils.consume(responseEntity); - return response; - } - - private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { - HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); - - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - - builder.addPart("file", new FileBody(file)); - builder.setContentType(ContentType.MULTIPART_FORM_DATA); - - HttpEntity multipart = builder.build(); - putRequest.setEntity(multipart); - return putRequest; - } - - private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; + @Test + public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist"); + assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); } @Test public void deleteFile() throws FileNotFoundException, IOException { - uploadFile(); + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -283,39 +249,38 @@ public void deleteFile() throws FileNotFoundException, IOException { @Test public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - uploadFile(); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); - assertEquals(HttpStatus.BAD_REQUEST.value(), response.getCode()); + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); } - private CloseableHttpResponse uploadFile() throws FileNotFoundException, IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodel4FileTest.json"); - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); - return uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + @Test + public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); } @Test public void getFile() throws FileNotFoundException, IOException, ParseException { - uploadFile(); + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); assertEquals(HttpStatus.OK.value(), response.getCode()); String received = BaSyxHttpTestUtils.getResponseAsString(response); String fileName = "BaSyx-Logo.png"; assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); - } - static String readFile(String path, Charset encoding) throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, encoding); + @Test + public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); } @Test - public void getFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - uploadFile(); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); - assertEquals(HttpStatus.BAD_REQUEST.value(), response.getCode()); + public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); } private String createSMEFileDeleteURL(String submodelId, String submodelElementIdShort) { @@ -663,4 +628,47 @@ protected List createSubmodels() { return Arrays.asList(DummySubmodelFactory.createTechnicalDataSubmodel(), DummySubmodelFactory.createOperationalDataSubmodel(), DummySubmodelFactory.createSimpleDataSubmodel()); } + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + String fileName = "BaSyx-Logo.png"; + + java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); + + HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); + + return executePutRequest(client, putRequest); + } + + private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { + HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + putRequest.setEntity(multipart); + return putRequest; + } + + private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; + } + + private static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java index 0b50e5191..0269903d6 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java @@ -25,9 +25,12 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -42,6 +45,7 @@ * */ public class TestSubmodelRepositorySubmodelElementsHTTP extends SubmodelRepositorySubmodelElementsTestSuiteHTTP { + private static final String SUBMODEL_JSON = "SingleSubmodel4FileTest.json"; private static ConfigurableApplicationContext appContext; @BeforeClass @@ -54,6 +58,12 @@ public void populateRepository() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); Collection submodels = createSubmodels(); submodels.forEach(repo::createSubmodel); + try { + createSubmodel4FileTest(); + } catch (IOException e) { + System.out.println("Cannot load "+SUBMODEL_JSON + " for testing file-upload feature."); + e.printStackTrace(); + } } @Override @@ -71,5 +81,10 @@ public static void shutdownAASRepo() { protected String getURL() { return "http://localhost:8080/submodels"; } + + private void createSubmodel4FileTest() throws FileNotFoundException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath(SUBMODEL_JSON); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + } } From 240fa1a398f3c34357f32145f579875ce336e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:02:53 +0200 Subject: [PATCH 10/18] move unit tests for file upload and download to SubmodelRepositorySubmodelHTTPTestSuite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../TestInMemorySubmodelRepository.java | 16 +- .../TestMongoDBSubmodelRepository.java | 9 +- .../DummySubmodelRepositoryComponent.java | 2 - ...bmodelRepositorySubmodelHTTPTestSuite.java | 152 ++++++++- ...ubmodelRepositorySubmodelElementsHTTP.java | 23 +- .../TestSubmodelRepositorySubmodelHTTP.java | 19 +- .../src/test/resources/MultipleSubmodels.json | 43 +++ .../resources/SingleSubmodelPaginated.json | 1 - .../test/resources/SubmodelsPaginated.json | 49 +++ ...lServiceSubmodelElementsTestSuiteHTTP.java | 292 ++++-------------- 10 files changed, 314 insertions(+), 292 deletions(-) delete mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json create mode 100644 basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java index 1d82a30c8..4fc12db12 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java @@ -44,7 +44,7 @@ * */ public class TestInMemorySubmodelRepository extends SubmodelRepositorySuite { - + private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; @Override @@ -56,14 +56,14 @@ protected SubmodelRepository getSubmodelRepository() { protected SubmodelRepository getSubmodelRepository(Collection submodels) { return new InMemorySubmodelRepository(new InMemorySubmodelServiceFactory(), submodels); } - + @Test - public void getConfiguredInMemorySmRepositoryName() { + public void getConfiguredInMemorySmRepositoryName() { SubmodelRepository repo = new InMemorySubmodelRepository(new InMemorySubmodelServiceFactory(), CONFIGURED_SM_REPO_NAME); - + assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } - + @Test(expected = CollidingIdentifierException.class) public void idCollisionDuringConstruction() { Collection submodelsWithCollidingIds = createSubmodelCollectionWithCollidingIds(); @@ -77,13 +77,11 @@ public void assertIdUniqueness() { } private Collection createSubmodelCollectionWithCollidingIds() { - return Arrays.asList(DummySubmodelFactory.createTechnicalDataSubmodel(), - DummySubmodelFactory.createTechnicalDataSubmodel()); + return Arrays.asList(DummySubmodelFactory.createTechnicalDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); } private Collection createSubmodelCollectionWithUniqueIds() { - return Arrays.asList(DummySubmodelFactory.createSimpleDataSubmodel(), - DummySubmodelFactory.createTechnicalDataSubmodel()); + return Arrays.asList(DummySubmodelFactory.createSimpleDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java index d83059e29..7bc9fc985 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java @@ -66,11 +66,11 @@ protected SubmodelRepository getSubmodelRepository(Collection submodel return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels, CONFIGURED_SM_REPO_NAME, GRIDFS_TEMPLATE).create(); } - + @Test public void getConfiguredMongoDBSmRepositoryName() { SubmodelRepository repo = new MongoDBSubmodelRepository(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME, GRIDFS_TEMPLATE); - + assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } @@ -87,10 +87,7 @@ public void invokeNonOperation() { } private void removeInvokableFromInvokableOperation(Submodel sm) { - sm.getSubmodelElements().stream() - .filter(InvokableOperation.class::isInstance) - .map(InvokableOperation.class::cast) - .forEach(o -> o.setInvokable(null)); + sm.getSubmodelElements().stream().filter(InvokableOperation.class::isInstance).map(InvokableOperation.class::cast).forEach(o -> o.setInvokable(null)); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java index 271786dff..9bc325618 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java @@ -28,8 +28,6 @@ import org.springframework.boot.SpringApplication; -import org.eclipse.digitaltwin.basyx.submodelrepository.InMemorySubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; /** diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index c80d287bd..2ea09e166 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -29,11 +29,24 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; @@ -42,6 +55,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -70,8 +84,7 @@ public void getAllSubmodelsPreconfigured() throws IOException, ParseException { @Test public void getSpecificSubmodel() throws ParseException, IOException { - String submodelJSON = requestSpecificSubmodelJSON(DummySubmodelFactory.createTechnicalDataSubmodel() - .getId()); + String submodelJSON = requestSpecificSubmodelJSON(DummySubmodelFactory.createTechnicalDataSubmodel().getId()); String expectedSubmodelJSON = getSingleSubmodelJSON(); BaSyxHttpTestUtils.assertSameJSONContent(expectedSubmodelJSON, submodelJSON); @@ -81,8 +94,7 @@ public void getSpecificSubmodel() throws ParseException, IOException { public void getSpecificSubmodelMetadata() throws ParseException, IOException { String expectedSubmodelJSON = getSingleSubmodelMetadataJSON(); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSubmodelMetadataURL(DummySubmodelFactory.createTechnicalDataSubmodel() - .getId())); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSubmodelMetadataURL(DummySubmodelFactory.createTechnicalDataSubmodel().getId())); assertEquals(HttpStatus.OK.value(), response.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(expectedSubmodelJSON, BaSyxHttpTestUtils.getResponseAsString(response)); @@ -149,8 +161,7 @@ public void createSubmodelCollidingId() throws IOException { @Test public void deleteSubmodel() throws IOException { - String existingSubmodelId = DummySubmodelFactory.createTechnicalDataSubmodel() - .getId(); + String existingSubmodelId = DummySubmodelFactory.createTechnicalDataSubmodel().getId(); CloseableHttpResponse deletionResponse = deleteSubmodelById(existingSubmodelId); assertEquals(HttpStatus.NO_CONTENT.value(), deletionResponse.getCode()); @@ -165,17 +176,132 @@ public void deleteNonExistingSubmodel() throws IOException { assertEquals(HttpStatus.NOT_FOUND.value(), deletionResponse.getCode()); } - - + @Test public void getPaginatedSubmodel() throws ParseException, IOException { - String submodelsJSON = BaSyxSubmodelHttpTestUtils - .requestAllSubmodels(getURL() + "?limit=1&cursor=7A7104BDAB57E184"); - String expected = getSingleSubmodelPaginatedJson(); + String submodelsJSON = BaSyxSubmodelHttpTestUtils.requestAllSubmodels(getURL() + "?limit=1&cursor=7A7104BDAB57E184"); + String expected = getSubmodelsPaginatedJson(); BaSyxHttpTestUtils.assertSameJSONContent(expected, submodelsJSON); } + @Test + public void uploadFileToFileSubmodelElement() throws IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist"); + assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void deleteFile() throws FileNotFoundException, IOException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void getFile() throws FileNotFoundException, IOException, ParseException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + String received = BaSyxHttpTestUtils.getResponseAsString(response); + + String fileName = "BaSyx-Logo.png"; + assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); + } + + @Test + public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + private String createSMEFileDeleteURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private String createSMEFileGetURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + String fileName = "BaSyx-Logo.png"; + + java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); + + HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); + + return executePutRequest(client, putRequest); + } + + private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { + HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + putRequest.setEntity(multipart); + return putRequest; + } + + private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; + } + + private static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + private void assertSubmodelCreationReponse(String submodelJSON, CloseableHttpResponse creationResponse) throws IOException, ParseException, JsonProcessingException, JsonMappingException { assertEquals(HttpStatus.CREATED.value(), creationResponse.getCode()); String response = BaSyxHttpTestUtils.getResponseAsString(creationResponse); @@ -228,8 +354,8 @@ private String getAllSubmodelJSON() throws IOException { return BaSyxHttpTestUtils.readJSONStringFromClasspath("MultipleSubmodels.json"); } - private String getSingleSubmodelPaginatedJson() throws FileNotFoundException, IOException { - return BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodelPaginated.json"); + private String getSubmodelsPaginatedJson() throws FileNotFoundException, IOException { + return BaSyxHttpTestUtils.readJSONStringFromClasspath("SubmodelsPaginated.json"); } protected List createSubmodels() { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java index 192e85d9c..2d3e9f00c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java @@ -26,12 +26,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Collection; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.http.SubmodelServiceSubmodelElementsTestSuiteHTTP; import org.junit.After; @@ -50,7 +45,6 @@ */ public class TestSubmodelRepositorySubmodelElementsHTTP extends SubmodelServiceSubmodelElementsTestSuiteHTTP { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); - private static final String SUBMODEL_JSON = "SingleSubmodel4FileTest.json"; private static ConfigurableApplicationContext appContext; @BeforeClass @@ -62,19 +56,12 @@ public static void startAASRepo() throws Exception { public void createSubmodelOnRepo() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); repo.createSubmodel(createSubmodel()); - try { - createSubmodel4FileTest(); - } catch (IOException e) { - System.out.println("Cannot load "+SUBMODEL_JSON + " for testing file-upload feature."); - e.printStackTrace(); - } } @After public void removeSubmodelFromRepo() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); - repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()) - .forEach(repo::deleteSubmodel); + repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()).forEach(repo::deleteSubmodel); } @AfterClass @@ -85,13 +72,7 @@ public static void shutdownAASRepo() { @Override protected String getURL() { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath("http://localhost:8080/submodels", - createSubmodel().getId()); - } - - private void createSubmodel4FileTest() throws FileNotFoundException, IOException { - String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath(SUBMODEL_JSON); - BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath("http://localhost:8080/submodels", createSubmodel().getId()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java index b7c267719..99689e9a5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java @@ -25,10 +25,13 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -43,6 +46,7 @@ */ public class TestSubmodelRepositorySubmodelHTTP extends SubmodelRepositorySubmodelHTTPTestSuite { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static final String SUBMODEL_JSON = "SingleSubmodel4FileTest.json"; private static ConfigurableApplicationContext appContext; @BeforeClass @@ -53,8 +57,7 @@ public static void startAASRepo() throws Exception { @Override public void resetRepository() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); - repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()) - .forEach(repo::deleteSubmodel); + repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()).forEach(repo::deleteSubmodel); } @Override @@ -62,6 +65,12 @@ public void populateRepository() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); Collection submodels = createSubmodels(); submodels.forEach(repo::createSubmodel); + try { + createSubmodel4FileTest(); + } catch (IOException e) { + System.out.println("Cannot load " + SUBMODEL_JSON + " for testing file-upload feature."); + e.printStackTrace(); + } } @AfterClass @@ -73,4 +82,10 @@ public static void shutdownAASRepo() { protected String getURL() { return "http://localhost:8080/submodels"; } + + private void createSubmodel4FileTest() throws FileNotFoundException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath(SUBMODEL_JSON); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + } + } \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json index 482e499ec..3c1f6b33a 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json @@ -406,6 +406,49 @@ } }, { + "id": "8A6344BDAB57E184", + "idShort": "FileTests", + "modelType": "Submodel", + "semanticId": { + "keys": [{ + "type": "GlobalReference", + "value": "0173-1#01-AFZ615#016" + } + ], + "type": "ExternalReference" + }, + "submodelElements": [{ + "category": "PARAMETER", + "contentType": "application/json", + "idShort": "FileData", + "modelType": "File", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "BaSyx-Logo.png" + }, { + "category": "PARAMETER", + "idShort": "NonFileParameter", + "modelType": "Property", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "5000", + "valueType": "xs:integer" + } + ] + }, + { "modelType": "Submodel", "id": "AC69B1CB44F07935", "idShort": "OperationalData", diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json deleted file mode 100644 index 342f34006..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json +++ /dev/null @@ -1 +0,0 @@ -{"paging_metadata":{"cursor":"AC69B1CB44F07935"},"result":[{"modelType":"Submodel","id":"AC69B1CB44F07935","idShort":"OperationalData","submodelElements":[{"modelType":"Property","value":"4370","valueType":"xs:integer","category":"VARIABLE","idShort":"RotationSpeed","semanticId":{"keys":[{"type":"ConceptDescription","value":"http://customer.com/cd/1/1/18EBD56F6B43D895"}],"type":"ExternalReference"}}]}]} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json new file mode 100644 index 000000000..af1ffc054 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json @@ -0,0 +1,49 @@ +{ + "paging_metadata": { + "cursor": "8A6344BDAB57E184" + }, + "result": [{ + "id": "8A6344BDAB57E184", + "idShort": "FileTests", + "modelType": "Submodel", + "semanticId": { + "keys": [{ + "type": "GlobalReference", + "value": "0173-1#01-AFZ615#016" + } + ], + "type": "ExternalReference" + }, + "submodelElements": [{ + "category": "PARAMETER", + "contentType": "application/json", + "idShort": "FileData", + "modelType": "File", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "BaSyx-Logo.png" + }, { + "category": "PARAMETER", + "idShort": "NonFileParameter", + "modelType": "Property", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "5000", + "valueType": "xs:integer" + } + ] + } + ] +} diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java index 14b622413..4cd81a8a4 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java @@ -29,31 +29,15 @@ import java.io.FileNotFoundException; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; - -import org.apache.hc.client5.http.ClientProtocolException; -import org.apache.hc.client5.http.classic.methods.HttpPut; -import org.apache.hc.client5.http.entity.mime.FileBody; -import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; + import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; import org.junit.Test; import org.springframework.http.HttpStatus; -import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -84,8 +68,7 @@ public void getSubmodelElements() throws FileNotFoundException, IOException, Par @Test public void getSubmodelElement() throws FileNotFoundException, IOException, ParseException { String expectedElement = getSubmodelElementJSON(); - CloseableHttpResponse response = requestSubmodelElement( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElement(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(expectedElement, BaSyxHttpTestUtils.getResponseAsString(response)); @@ -95,8 +78,7 @@ public void getSubmodelElement() throws FileNotFoundException, IOException, Pars public void getPropertyValue() throws IOException, ParseException { String expectedValue = wrapStringValue("5000"); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -114,20 +96,17 @@ public void getNonExistingSubmodelElementValue() throws IOException { public void setPropertyValue() throws IOException, ParseException { String expectedValue = wrapStringValue("2567"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getMultiLanguagePropertyValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -140,20 +119,17 @@ public void getMultiLanguagePropertyValue() throws IOException, ParseException { public void setMultiLanguagePropertyValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setMultiLanguagePropertyValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getRangeValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -166,20 +142,17 @@ public void getRangeValue() throws IOException, ParseException { public void setRangeValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setRangeValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getFileValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -192,91 +165,16 @@ public void getFileValue() throws IOException, ParseException { public void setFileValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setFileValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, expectedValue); assertEquals(HttpStatus.OK.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } - @Test - public void uploadFileToFileSubmodelElement() throws IOException { - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); - - assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); - } - - @Test - public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); - assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); - } - - @Test - public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist"); - assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); - } - - @Test - public void deleteFile() throws FileNotFoundException, IOException { - uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); - - CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); - assertEquals(HttpStatus.OK.value(), response.getCode()); - - response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); - assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); - } - - @Test - public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); - assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); - } - - @Test - public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); - assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); - } - - @Test - public void getFile() throws FileNotFoundException, IOException, ParseException { - uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); - assertEquals(HttpStatus.OK.value(), response.getCode()); - String received = BaSyxHttpTestUtils.getResponseAsString(response); - - String fileName = "BaSyx-Logo.png"; - assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); - } - - @Test - public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); - assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); - } - - @Test - public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); - assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); - } - - private String createSMEFileDeleteURL(String submodelId, String submodelElementIdShort) { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; - } - - private String createSMEFileGetURL(String submodelId, String submodelElementIdShort) { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; - } - @Test public void getBlobValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -289,20 +187,17 @@ public void getBlobValue() throws IOException, ParseException { public void setBlobValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setBlobValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getEntityValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -315,12 +210,10 @@ public void getEntityValue() throws IOException, ParseException { public void setEntityValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setEntityValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @@ -330,20 +223,17 @@ public void setEntityValueMRP() throws IOException, ParseException { String minimumRequestPayloadValue = getJSONValueAsString("value/setEntityValueMRP.json"); String expectedValue = getJSONValueAsString("value/expectedUpdatedMRPEntityValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, minimumRequestPayloadValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, minimumRequestPayloadValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getReferenceElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -356,20 +246,17 @@ public void getReferenceElementValue() throws IOException, ParseException { public void setReferenceElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setReferenceElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getRelationshipElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -382,20 +269,17 @@ public void getRelationshipElementValue() throws IOException, ParseException { public void setRelationshipElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setRelationshipElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getAnnotatedRelationshipElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -408,20 +292,17 @@ public void getAnnotatedRelationshipElementValue() throws IOException, ParseExce public void setAnnotatedRelationshipElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setAnnotatedRelationshipElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getSubmodelElementCollectionValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -434,20 +315,17 @@ public void getSubmodelElementCollectionValue() throws IOException, ParseExcepti public void setSubmodelElementCollectionValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setSubmodelElementCollectionValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getSubmodelElementListValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -460,12 +338,10 @@ public void getSubmodelElementListValue() throws IOException, ParseException { public void setSubmodelElementListValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setSubmodelElementListValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @@ -473,8 +349,7 @@ public void setSubmodelElementListValue() throws IOException, ParseException { @Test public void createSubmodelElementCollidingId() throws IOException { String element = getJSONValueAsString("SubmodelElement.json"); - CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), - element); + CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), element); assertEquals(HttpStatus.CONFLICT.value(), createdResponse.getCode()); } @@ -482,59 +357,45 @@ public void createSubmodelElementCollidingId() throws IOException { @Test public void createSubmodelElement() throws FileNotFoundException, IOException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), - element); + CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), element); - CloseableHttpResponse fetchedResponse = requestSubmodelElement( - "MaxRotationSpeedNew"); + CloseableHttpResponse fetchedResponse = requestSubmodelElement("MaxRotationSpeedNew"); assertEquals(HttpStatus.CREATED.value(), createdResponse.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedResponse)); } @Test public void deleteSubmodelElement() throws FileNotFoundException, IOException, ParseException { - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT)); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); - CloseableHttpResponse fetchedResponse = requestSubmodelElement( - DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT); + CloseableHttpResponse fetchedResponse = requestSubmodelElement(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT); assertEquals(HttpStatus.NOT_FOUND.value(), fetchedResponse.getCode()); } @Test - public void createNestedSubmodelElementInSubmodelElementCollection() - throws FileNotFoundException, IOException, ParseException { + public void createNestedSubmodelElementInSubmodelElementCollection() throws FileNotFoundException, IOException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdInCollectionResponse = BaSyxHttpTestUtils.executePostOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_COLLECTION_SIMPLE), element); + CloseableHttpResponse createdInCollectionResponse = BaSyxHttpTestUtils.executePostOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_COLLECTION_SIMPLE), element); assertEquals(HttpStatus.CREATED.value(), createdInCollectionResponse.getCode()); - CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement( - createCollectionNestedIdShortPath("MaxRotationSpeedNew")); - BaSyxHttpTestUtils.assertSameJSONContent(element, - BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInCollectionResponse)); + CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement(createCollectionNestedIdShortPath("MaxRotationSpeedNew")); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInCollectionResponse)); } @Test - public void createNestedSubmodelElementInSubmodelElementList() - throws IOException, JsonProcessingException, JsonMappingException, ParseException { + public void createNestedSubmodelElementInSubmodelElementList() throws IOException, JsonProcessingException, JsonMappingException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdInListResponse = BaSyxHttpTestUtils.executePostOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_LIST_SIMPLE), element); + CloseableHttpResponse createdInListResponse = BaSyxHttpTestUtils.executePostOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_LIST_SIMPLE), element); assertEquals(HttpStatus.CREATED.value(), createdInListResponse.getCode()); CloseableHttpResponse fetchedNestedInListResponse = requestSubmodelElement(createListNestedIdShortPath(1)); - BaSyxHttpTestUtils.assertSameJSONContent(element, - BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInListResponse)); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInListResponse)); } @Test - public void deleteNestedSubmodelElementFromSubmodelElementCollection() - throws FileNotFoundException, IOException, ParseException { - String nestedIdShortPathInCollection = createCollectionNestedIdShortPath( - DummySubmodelFactory.SUBMODEL_ELEMENT_FIRST_ID_SHORT); - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils - .executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortPathInCollection)); + public void deleteNestedSubmodelElementFromSubmodelElementCollection() throws FileNotFoundException, IOException, ParseException { + String nestedIdShortPathInCollection = createCollectionNestedIdShortPath(DummySubmodelFactory.SUBMODEL_ELEMENT_FIRST_ID_SHORT); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortPathInCollection)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement(nestedIdShortPathInCollection); @@ -542,11 +403,9 @@ public void deleteNestedSubmodelElementFromSubmodelElementCollection() } @Test - public void deleteNestedSubmodelElementFromSubmodelElementList() - throws IOException { + public void deleteNestedSubmodelElementFromSubmodelElementList() throws IOException { String nestedIdShortSmeList = createListNestedIdShortPath(0); - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils - .executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortSmeList)); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortSmeList)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); CloseableHttpResponse fetchedNestedInListResponse = requestSubmodelElement(createListNestedIdShortPath(1)); @@ -656,47 +515,4 @@ private String getJSONValueAsString(String fileName) throws FileNotFoundExceptio return BaSyxHttpTestUtils.readJSONStringFromClasspath(fileName); } - private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { - CloseableHttpClient client = HttpClients.createDefault(); - - String fileName = "BaSyx-Logo.png"; - - java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); - - HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); - - return executePutRequest(client, putRequest); - } - - private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { - CloseableHttpResponse response = client.execute(putRequest); - - HttpEntity responseEntity = response.getEntity(); - - EntityUtils.consume(responseEntity); - return response; - } - - private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { - HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); - - MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - - builder.addPart("file", new FileBody(file)); - builder.setContentType(ContentType.MULTIPART_FORM_DATA); - - HttpEntity multipart = builder.build(); - putRequest.setEntity(multipart); - return putRequest; - } - - private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; - } - - private static String readFile(String path, Charset encoding) throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, encoding); - } - } From c3aa7e17767772377f51d86b9249f4e6064c02ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:20:26 +0200 Subject: [PATCH 11/18] Change expected value of the assertion for the setFileValue unit test. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java index 4cd81a8a4..5e81bf028 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java @@ -166,7 +166,7 @@ public void setFileValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setFileValue.json"); CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, expectedValue); - assertEquals(HttpStatus.OK.value(), writeResponse.getCode()); + assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); From d8dac1d759934d5ffdbf4cdaee2ff5f3b1227adc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:52:27 +0200 Subject: [PATCH 12/18] Adjust assertion in unit test getDefaultSubmodelRepositoryName() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../basyx/submodelrepository/core/SubmodelRepositorySuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 67501cc7c..947ecd855 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -425,7 +425,7 @@ public void deleteNestedSubmodelElementInSubmodelElementList() { public void getDefaultSubmodelRepositoryName() { SubmodelRepository repo = getSubmodelRepository(); - assertEquals("sm-repo", repo.getName()); + assertEquals("configured-sm-repo-name", repo.getName()); } @Test From 37e2e75d45426f8e46bbb8dcfd4b540ecc835be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:14:55 +0200 Subject: [PATCH 13/18] Add new cosntructor in MongoDBSubmodelRepositoryFactory which uses the default repository name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../MongoDBSubmodelRepositoryFactory.java | 33 ++++++++++--------- .../TestMongoDBSubmodelRepository.java | 2 +- .../core/SubmodelRepositorySuite.java | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index 712063ad2..0dffb6e59 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -49,44 +49,45 @@ public class MongoDBSubmodelRepositoryFactory implements SubmodelRepositoryFacto private String collectionName; private SubmodelServiceFactory submodelServiceFactory; private Collection submodels; - + private String smRepositoryName; private GridFsTemplate gridFsTemplate; @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory) { + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory) { this.mongoTemplate = mongoTemplate; this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; } - - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { + + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; } + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + GridFsTemplate gridFsTemplate) { + this(mongoTemplate, collectionName, submodelServiceFactory); + this.gridFsTemplate = gridFsTemplate; + } + @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels) { + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + Collection submodels) { this(mongoTemplate, collectionName, submodelServiceFactory); this.submodels = submodels; } - + @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + Collection submodels, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory, submodels); this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; } - + @Override public SubmodelRepository create() { if (this.submodels == null || this.submodels.isEmpty()) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java index 7bc9fc985..3fb882337 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java @@ -54,7 +54,7 @@ public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { protected SubmodelRepository getSubmodelRepository() { MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME, GRIDFS_TEMPLATE).create(); + return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, GRIDFS_TEMPLATE).create(); } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 947ecd855..67501cc7c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -425,7 +425,7 @@ public void deleteNestedSubmodelElementInSubmodelElementList() { public void getDefaultSubmodelRepositoryName() { SubmodelRepository repo = getSubmodelRepository(); - assertEquals("configured-sm-repo-name", repo.getName()); + assertEquals("sm-repo", repo.getName()); } @Test From 4cc720b953189e7ea13d74811d7bba6052af3bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:33:35 +0200 Subject: [PATCH 14/18] Put the private mathod after the unit tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../core/SubmodelRepositorySuite.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 67501cc7c..be5c831b5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -276,16 +276,7 @@ public void getNonExistingFile() throws IOException { repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); } - - private void deleteFileIfExisted(SubmodelRepository repo) { - try { - repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); - repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); - }catch(FileDoesNotExistException e) { - return; - } - - } + @Test(expected = ElementNotAFileException.class) public void getFileFromNonFileSME() throws IOException { @@ -460,7 +451,16 @@ public void invokeNonOperation() { submodelRepo.invokeOperation(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, new OperationVariable[0]); } - + + private void deleteFileIfExisted(SubmodelRepository repo) { + try { + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + }catch(FileDoesNotExistException e) { + return; + } + + } private SubmodelElement getExpectedSubmodelElement() { return DummySubmodelFactory.createOperationalDataSubmodel() From 073e9389f3450c9d886349fa701a2b43239e70ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:09:03 +0200 Subject: [PATCH 15/18] Delete unnecessary exception throw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../FeatureNotImplementedException.java | 31 +++++++ .../exceptions/FileHandlingException.java | 7 ++ .../pom.xml | 3 +- .../InMemorySubmodelRepository.java | 74 +++++++++-------- .../MongoDBSubmodelRepository.java | 80 +++++++------------ .../MongoDBSubmodelRepositoryFactory.java | 3 +- .../SubmodelRepository.java | 7 +- .../core/SubmodelRepositorySuite.java | 74 ++++++++++++++--- .../feature/mqtt/MqttSubmodelRepository.java | 4 +- .../SubmodelRepositoryApiHTTPController.java | 5 +- .../InMemorySubmodelService.java | 1 - 11 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java index 4cfce38ee..193d8b0bf 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java @@ -1,5 +1,36 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.core.exceptions; +/** + * This exception is used for features where certain functionalities are not implemented yet. + * + * @author zhangzai + * + */ @SuppressWarnings("serial") public class FeatureNotImplementedException extends RuntimeException { } diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java new file mode 100644 index 000000000..4dae57a49 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java @@ -0,0 +1,7 @@ +package org.eclipse.digitaltwin.basyx.core.exceptions; + +@SuppressWarnings("serial") +public class FileHandlingException extends RuntimeException { + public FileHandlingException() { + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml index 66664ad2c..170fe105e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml @@ -1,6 +1,5 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 82566f8b9..ce6f7a434 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -50,6 +50,7 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; @@ -83,13 +84,14 @@ public class InMemorySubmodelRepository implements SubmodelRepository { public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory) { this.submodelServiceFactory = submodelServiceFactory; } - + /** * Creates the InMemorySubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices * - * @param submodelServiceFactory - * @param smRepositoryName Name of the SubmodelRepository + * @param submodelServiceFactory + * @param smRepositoryName + * Name of the SubmodelRepository */ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, String smRepositoryName) { this(submodelServiceFactory); @@ -110,15 +112,16 @@ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, submodelServices = createServices(submodels); } - + /** * Creates the InMemorySubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and preconfiguring * it with the passed Submodels * - * @param submodelServiceFactory - * @param submodels - * @param smRepositoryName Name of the SubmodelRepository + * @param submodelServiceFactory + * @param submodels + * @param smRepositoryName + * Name of the SubmodelRepository */ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName) { this(submodelServiceFactory, submodels); @@ -142,13 +145,9 @@ private Map createServices(Collection submode @Override public CursorResult> getAllSubmodels(PaginationInfo pInfo) { - List allSubmodels = submodelServices.values() - .stream() - .map(service -> service.getSubmodel()) - .collect(Collectors.toList()); + List allSubmodels = submodelServices.values().stream().map(service -> service.getSubmodel()).collect(Collectors.toList()); - TreeMap submodelMap = allSubmodels.stream() - .collect(Collectors.toMap(Submodel::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + TreeMap submodelMap = allSubmodels.stream().collect(Collectors.toMap(Submodel::getId, aas -> aas, (a, b) -> a, TreeMap::new)); PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, Submodel::getId); CursorResult> paginatedSubmodels = paginationSupport.getPaged(pInfo); @@ -231,7 +230,7 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { submodel.setSubmodelElements(null); return submodel; } - + @Override public String getName() { return smRepositoryName == null ? SubmodelRepository.super.getName() : smRepositoryName; @@ -242,7 +241,6 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath return getSubmodelService(submodelId).invokeOperation(idShortPath, input); } - @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { throwIfSubmodelDoesNotExist(submodelId); @@ -250,35 +248,51 @@ public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throwIfSmElementIsNotAFile(submodelElement); - File fileSmElement = (File) submodelElement; - String filePath = fileSmElement.getValue(); + String filePath = getFilePath(submodelElement); - throwIfFileDoesNotExist(fileSmElement, filePath); + throwIfFileDoesNotExist((File) submodelElement, filePath); return new java.io.File(filePath); } + private String getFilePath(SubmodelElement submodelElement) { + return ((File) submodelElement).getValue(); + } + @Override - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException { + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) { throwIfSubmodelDoesNotExist(submodelId); SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement, inputStream); + throwIfSmElementIsNotAFile(submodelElement); File fileSmElement = (File) submodelElement; - String filePath = getFilePath(submodelId, idShortPath, fileSmElement); + deleteExistingFile(fileSmElement); + String filePath = createFilePath(submodelId, idShortPath, fileName); java.io.File targetFile = new java.io.File(filePath); try (FileOutputStream outStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outStream); + } catch (IOException e) { + throw new FileHandlingException(); } FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); setSubmodelElementValue(submodelId, idShortPath, fileValue); + } - inputStream.close(); + private void deleteExistingFile(File fileSmElement) { + String filePath = fileSmElement.getValue(); + if(filePath.isEmpty()) return; + + try { + Files.deleteIfExists(Paths.get(filePath, "")); + } catch (IOException e) { + e.printStackTrace(); + } + } @Override @@ -301,19 +315,10 @@ public void deleteFileValue(String submodelId, String idShortPath) { setSubmodelElementValue(submodelId, idShortPath, fileValue); } - private String getFilePath(String submodelId, String idShortPath, File file) { - String fileName = submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + file.getValue(); - - return tmpDirectory + "/" + fileName; + private String createFilePath(String submodelId, String idShortPath, String fileName) { + return tmpDirectory + "/" + submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + fileName; } - - private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { - if (!(submodelElement instanceof File)) { - inputStream.close(); - throw new ElementNotAFileException(submodelElement.getIdShort()); - } - } - + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { if (!(submodelElement instanceof File)) throw new ElementNotAFileException(submodelElement.getIdShort()); @@ -326,7 +331,6 @@ private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { throw new IdentificationMismatchException(); } - private SubmodelService getSubmodelService(String submodelId) { throwIfSubmodelDoesNotExist(submodelId); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 83a43b3f1..24255e905 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -95,8 +95,7 @@ public class MongoDBSubmodelRepository implements SubmodelRepository { * @param collectionName * @param submodelServiceFactory */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory) { this.mongoTemplate = mongoTemplate; this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; @@ -110,13 +109,13 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * SubmodelServiceFactory for creating new SubmodelServices and uses a * collectionName and a mongoTemplate for operating MongoDB * - * @param mongoTemplate - * @param collectionName - * @param submodelServiceFactory - * @param smRepositoryName Name of the SubmodelRepository + * @param mongoTemplate + * @param collectionName + * @param submodelServiceFactory + * @param smRepositoryName + * Name of the SubmodelRepository */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, String smRepositoryName, GridFsTemplate gridFsTemplate) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; @@ -136,8 +135,7 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * @param submodelServiceFactory * @param submodels */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, Collection submodels) { this(mongoTemplate, collectionName, submodelServiceFactory); initializeRemoteCollection(submodels); @@ -148,19 +146,19 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * Creates the MongoDBSubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and uses a * collectionName and a mongoTemplate for operating MongoDB. Additionally - * initializes the MongoDB collection with a collection of submodels. - * And configures the SubmodelRepository name. + * initializes the MongoDB collection with a collection of submodels. And + * configures the SubmodelRepository name. * * @param mongoTemplate * @param collectionName * @param submodelServiceFactory * @param submodels - * @param smRepositoryName Name of the SubmodelRepository + * @param smRepositoryName + * Name of the SubmodelRepository */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName, GridFsTemplate gridFsTemplate) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory, submodels); - + this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; @@ -193,8 +191,7 @@ public CursorResult> getAllSubmodels(PaginationInfo pInfo) { @Override public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistException { - Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), - Submodel.class, collectionName); + Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), Submodel.class, collectionName); if (submodel == null) { throw new ElementDoesNotExistException(submodelId); } @@ -233,8 +230,7 @@ public void createSubmodel(Submodel submodel) throws CollidingIdentifierExceptio } private void throwIfCollidesWithRemoteId(Submodel submodel) { - if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodel.getId())), - Submodel.class, collectionName)) { + if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodel.getId())), Submodel.class, collectionName)) { throw new CollidingIdentifierException(submodel.getId()); } } @@ -244,26 +240,22 @@ private SubmodelService getSubmodelService(String submodelId) { } @Override - public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) - throws ElementDoesNotExistException { + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElements(pInfo); } @Override - public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) - throws ElementDoesNotExistException { + public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElement(submodelElementIdShort); } @Override - public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) - throws ElementDoesNotExistException { + public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElementValue(submodelElementIdShort); } @Override - public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) - throws ElementDoesNotExistException { + public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.setSubmodelElementValue(submodelElementIdShort, value); @@ -272,8 +264,7 @@ public void setSubmodelElementValue(String submodelId, String submodelElementIdS @Override public void deleteSubmodel(String submodelId) throws ElementDoesNotExistException { - DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), - Submodel.class, collectionName); + DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), Submodel.class, collectionName); if (result.getDeletedCount() == 0) { throw new ElementDoesNotExistException(submodelId); @@ -290,8 +281,7 @@ public void createSubmodelElement(String submodelId, SubmodelElement submodelEle } @Override - public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) - throws ElementDoesNotExistException { + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.createSubmodelElement(idShortPath, submodelElement); @@ -321,10 +311,9 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot @Override public String getName() { return smRepositoryName == null ? SubmodelRepository.super.getName() : smRepositoryName; - } - - private String resolveCursor(PaginationInfo pRequest, List foundDescriptors, - Function idResolver) { + } + + private String resolveCursor(PaginationInfo pRequest, List foundDescriptors, Function idResolver) { if (foundDescriptors.isEmpty() || !pRequest.isPaged()) { return null; } @@ -397,28 +386,24 @@ public void deleteFileValue(String submodelId, String idShortPath) { } @Override - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) - throws ElementDoesNotExistException, IdentificationMismatchException, IOException { + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) { Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); throwIfSubmodelDoesNotExist(query, submodelId); SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement, inputStream); + throwIfSmElementIsNotAFile(submodelElement); File fileSmElement = (File) submodelElement; ObjectId id = gridFsTemplate.store(inputStream, fileSmElement.getValue(), fileSmElement.getContentType()); - FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), - appendFsIdToFileValue(fileSmElement, id)); + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), appendFsIdToFileValue(fileSmElement, id)); setSubmodelElementValue(submodelId, idShortPath, fileValue); - - inputStream.close(); } - + private void configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { this.gridFsTemplate = new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); } @@ -481,13 +466,6 @@ private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { if (!(submodelElement instanceof File)) throw new ElementNotAFileException(submodelElement.getIdShort()); } - - private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement, InputStream inputStream) throws IOException { - if (!(submodelElement instanceof File)) { - inputStream.close(); - throw new ElementNotAFileException(submodelElement.getIdShort()); - } - } private String getFilePath(String tmpDirectory, String idShortPath, String contentType) { String fileName = idShortPath.replace("/", "-"); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index 0dffb6e59..9a3d8cea2 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -66,7 +66,8 @@ public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${b this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; } - + + @Autowired(required = false) public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index 53615f86a..841b46d84 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -28,7 +28,6 @@ import java.util.List; import java.io.IOException; import java.io.InputStream; -import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -222,7 +221,7 @@ public default String getName() { * @return * @throws ElementDoesNotExistException */ - public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException; + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath); /** * Uploads a file to a file submodelelement @@ -237,7 +236,7 @@ public default String getName() { * @throws ElementDoesNotExistException * @throws IOException */ - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException; + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream); /** * Deletes the file of a file submodelelement @@ -249,5 +248,5 @@ public default String getName() { * @return * @throws ElementDoesNotExistException */ - public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException; + public void deleteFileValue(String submodelId, String idShortPath); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index be5c831b5..beca6aacc 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -69,6 +70,7 @@ */ public abstract class SubmodelRepositorySuite { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static final String DUMMY_FILE_CONTENT = "this is a file"; protected abstract SubmodelRepository getSubmodelRepository(); @@ -195,7 +197,6 @@ public void getSubmodelElements() { public void getSubmodelElementsOfNonExistingSubmodel() { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); repo.getSubmodelElements("notExisting", NO_LIMIT_PAGINATION_INFO).getResult(); - } @Test @@ -254,23 +255,67 @@ public void setPropertyValue() { } @Test - public void getFile() throws IOException { + public void updateFile() { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - String expectedFileExtension = "json"; - InputStream expectedFile = getInputStreamOfFileFromClasspath("SampleJsonFile.json"); + // Set the value of the file-submodelelement for the first time + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } + + // Set the value of the file-submodel element again with a dummy text file + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "newFile.txt", getInputStreamOfDummyFile()); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } + + // Get the file from the file submodel element + File retrievedValue = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + try { + String actual = new String(FileUtils.openInputStream(retrievedValue).readAllBytes()); + assertEquals(DUMMY_FILE_CONTENT, actual); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void getFile(){ + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + String expectedFileExtension = "json"; - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + InputStream expectedFile = null; + try { + expectedFile = getInputStreamOfFileFromClasspath("SampleJsonFile.json"); + } catch (IOException e1) { + e1.printStackTrace(); + } + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e) { + fail(); + e.printStackTrace(); + } File retrievedValue = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); assertEquals(expectedFileExtension, getExtension(retrievedValue.getName())); - assertTrue(IOUtils.contentEquals(expectedFile, FileUtils.openInputStream(retrievedValue))); + try { + assertTrue(IOUtils.contentEquals(expectedFile, FileUtils.openInputStream(retrievedValue))); + } catch (IOException e) { + e.printStackTrace(); + } } @Test(expected = FileDoesNotExistException.class) - public void getNonExistingFile() throws IOException { + public void getNonExistingFile(){ SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); deleteFileIfExisted(repo); @@ -279,17 +324,22 @@ public void getNonExistingFile() throws IOException { @Test(expected = ElementNotAFileException.class) - public void getFileFromNonFileSME() throws IOException { + public void getFileFromNonFileSME(){ SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); } @Test - public void deleteFile() throws IOException { + public void deleteFile(){ SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); @@ -501,6 +551,10 @@ private InputStream getInputStreamOfFileFromClasspath(String fileName) throws Fi return classPathResource.getInputStream(); } + private InputStream getInputStreamOfDummyFile() throws FileNotFoundException, IOException { + return new ByteArrayInputStream(DUMMY_FILE_CONTENT.getBytes()); + } + private String getExtension(String filename) { return FilenameUtils.getExtension(filename); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index c571fb679..d44fcd465 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -1,9 +1,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; import java.util.List; -import java.io.IOException; import java.io.InputStream; -import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -201,7 +199,7 @@ public void deleteFileValue(String identifier, String idShortPath) { } @Override - public void setFileValue(String submodelId, String idShortPath, InputStream inputStream) throws IOException { + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream){ throw new FeatureNotImplementedException(); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 1b3abba1f..f104f86e2 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -28,6 +28,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -200,7 +201,9 @@ public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submode @Override public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { try { - repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, file.getInputStream()); + InputStream fileInputstream = file.getInputStream(); + repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, fileName, fileInputstream); + fileInputstream.close(); return new ResponseEntity(HttpStatus.OK); } catch (ElementDoesNotExistException e) { return new ResponseEntity(HttpStatus.NOT_FOUND); diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java index 6e6dcfb8b..87af62974 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java @@ -165,7 +165,6 @@ private void deleteNestedSubmodelElement(String idShortPath) { deleteNestedSubmodelElementFromList(idShortPath, sm); } else { deleteNestedSubmodelElementFromCollection(idShortPath, sm); - return; } } From 008bffe1dc44ce1fe20dbe1bb781c78799298e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zai=20M=C3=BCller-Zhang?= <97607180+zhangzai123@users.noreply.github.com> Date: Mon, 9 Oct 2023 09:53:19 +0200 Subject: [PATCH 16/18] add @autowire=false to the constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Zai Müller-Zhang <97607180+zhangzai123@users.noreply.github.com> --- .../submodelrepository/MongoDBSubmodelRepositoryFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index 9a3d8cea2..3b5cdb54f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -59,7 +59,8 @@ public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${b this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; } - + + @Autowired(required = false) public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); From 1bf4134ea9c58daf0870bbfa7a6a2368ca20b5de Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish Date: Mon, 9 Oct 2023 17:07:21 +0200 Subject: [PATCH 17/18] Handles correct closing of input streams - Minor refactoring Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../FeatureNotImplementedException.java | 12 ++++++ .../exceptions/FileHandlingException.java | 42 +++++++++++++++++++ .../InMemorySubmodelRepository.java | 3 +- .../SubmodelRepository.java | 20 +++++---- .../core/SubmodelRepositorySuite.java | 2 +- .../SubmodelRepositoryApiHTTPController.java | 19 ++++++++- 6 files changed, 87 insertions(+), 11 deletions(-) diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java index 193d8b0bf..1d13ab9b3 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java @@ -33,4 +33,16 @@ */ @SuppressWarnings("serial") public class FeatureNotImplementedException extends RuntimeException { + + public FeatureNotImplementedException() { + super(); + } + + public FeatureNotImplementedException(String featureName) { + super(getMessage(featureName)); + } + + private static String getMessage(String featureName) { + return "Feature " + featureName + " is not implemented yet"; + } } diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java index 4dae57a49..8d7b9c11b 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java @@ -1,7 +1,49 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.core.exceptions; +/** + * Indicates that the provided file could not be handled + * + * @author zhangzai + * + */ @SuppressWarnings("serial") public class FileHandlingException extends RuntimeException { + public FileHandlingException() { + super(); + } + + public FileHandlingException(String fileName) { + super(getMessage(fileName)); + } + + private static String getMessage(String fileName) { + return "Exception occurred while handling the file: " + fileName; } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index ce6f7a434..09db7e3df 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -244,6 +244,7 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { throwIfSubmodelDoesNotExist(submodelId); + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); throwIfSmElementIsNotAFile(submodelElement); @@ -275,7 +276,7 @@ public void setFileValue(String submodelId, String idShortPath, String fileName, try (FileOutputStream outStream = new FileOutputStream(targetFile)) { IOUtils.copy(inputStream, outStream); } catch (IOException e) { - throw new FileHandlingException(); + throw new FileHandlingException(fileName); } FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index 841b46d84..fbe200ae9 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -26,7 +26,6 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; import java.util.List; -import java.io.IOException; import java.io.InputStream; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; @@ -34,6 +33,8 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; @@ -219,9 +220,12 @@ public default String getName() { * @param idShortPath * the IdShort of the file element * @return + * * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException */ - public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath); + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; /** * Uploads a file to a file submodelelement @@ -232,11 +236,11 @@ public default String getName() { * the IdShort of the file element * @param file * the file object to upload - * @return + * * @throws ElementDoesNotExistException - * @throws IOException + * @throws ElementNotAFileException */ - public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream); + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException; /** * Deletes the file of a file submodelelement @@ -245,8 +249,10 @@ public default String getName() { * the Submodel id * @param idShortPath * the IdShort of the file element - * @return + * * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException */ - public void deleteFileValue(String submodelId, String idShortPath); + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index beca6aacc..1224b7cb8 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -331,7 +331,7 @@ public void getFileFromNonFileSME(){ } @Test - public void deleteFile(){ + public void deleteFile() { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); try { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index f104f86e2..7cde8a23e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -200,16 +200,20 @@ public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submode @Override public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { + InputStream fileInputstream = null; try { - InputStream fileInputstream = file.getInputStream(); + fileInputstream = file.getInputStream(); repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, fileName, fileInputstream); - fileInputstream.close(); + closeInputStream(fileInputstream); return new ResponseEntity(HttpStatus.OK); } catch (ElementDoesNotExistException e) { + closeInputStream(fileInputstream); return new ResponseEntity(HttpStatus.NOT_FOUND); } catch (ElementNotAFileException e) { + closeInputStream(fileInputstream); return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); } catch (IOException e) { + closeInputStream(fileInputstream); return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -254,4 +258,15 @@ private OperationResult createOperationResult(OperationVariable[] result) { operationResult.setOutputArguments(Arrays.asList(result)); return operationResult; } + + private void closeInputStream(InputStream fileInputstream) { + if (fileInputstream == null) + return; + + try { + fileInputstream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } From fd010e0be7d42f5e874754ee947065ccd94773b8 Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish Date: Mon, 9 Oct 2023 18:25:48 +0200 Subject: [PATCH 18/18] Refactors code Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../InMemorySubmodelRepository.java | 56 ++++++++++--------- .../MongoDBSubmodelRepository.java | 7 +-- .../MongoDBSubmodelRepositoryFactory.java | 13 ++--- .../SubmodelRepository.java | 6 +- .../core/SubmodelRepositorySuite.java | 3 +- ...bmodelRepositorySubmodelHTTPTestSuite.java | 13 +++++ .../TestSubmodelRepositorySubmodelHTTP.java | 1 + 7 files changed, 58 insertions(+), 41 deletions(-) diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index 18390009f..165ac560b 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -68,7 +68,7 @@ /** * In-memory implementation of the SubmodelRepository * - * @author schnicke, danish, kammognie + * @author schnicke, danish, kammognie, zhangzai * */ public class InMemorySubmodelRepository implements SubmodelRepository { @@ -266,11 +266,6 @@ public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throwIfFileDoesNotExist((File) submodelElement, filePath); return new java.io.File(filePath); - - } - - private String getFilePath(SubmodelElement submodelElement) { - return ((File) submodelElement).getValue(); } @Override @@ -283,34 +278,18 @@ public void setFileValue(String submodelId, String idShortPath, String fileName, File fileSmElement = (File) submodelElement; deleteExistingFile(fileSmElement); String filePath = createFilePath(submodelId, idShortPath, fileName); - java.io.File targetFile = new java.io.File(filePath); - - try (FileOutputStream outStream = new FileOutputStream(targetFile)) { - IOUtils.copy(inputStream, outStream); - } catch (IOException e) { - throw new FileHandlingException(fileName); - } + + createFileAtSpecifiedPath(fileName, inputStream, filePath); FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); setSubmodelElementValue(submodelId, idShortPath, fileValue); } - private void deleteExistingFile(File fileSmElement) { - String filePath = fileSmElement.getValue(); - if(filePath.isEmpty()) return; - - try { - Files.deleteIfExists(Paths.get(filePath, "")); - } catch (IOException e) { - e.printStackTrace(); - } - - } - @Override public void deleteFileValue(String submodelId, String idShortPath) { throwIfSubmodelDoesNotExist(submodelId); + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); throwIfSmElementIsNotAFile(submodelElement); @@ -329,7 +308,7 @@ public void deleteFileValue(String submodelId, String idShortPath) { } private String createFilePath(String submodelId, String idShortPath, String fileName) { - return tmpDirectory + "/" + submodelId + "-" + idShortPath.replaceAll("/", "-") + "-" + fileName; + return tmpDirectory + "/" + submodelId + "-" + idShortPath.replace("/", "-") + "-" + fileName; } private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { @@ -383,5 +362,30 @@ private String getTemporaryDirectoryPath() { } return tempDirectoryPath; } + + private String getFilePath(SubmodelElement submodelElement) { + return ((File) submodelElement).getValue(); + } + + private void deleteExistingFile(File fileSmElement) { + String filePath = fileSmElement.getValue(); + if(filePath.isEmpty()) return; + + try { + Files.deleteIfExists(Paths.get(filePath, "")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void createFileAtSpecifiedPath(String fileName, InputStream inputStream, String filePath) { + java.io.File targetFile = new java.io.File(filePath); + + try (FileOutputStream outStream = new FileOutputStream(targetFile)) { + IOUtils.copy(inputStream, outStream); + } catch (IOException e) { + throw new FileHandlingException(fileName); + } + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index 24255e905..5b490a62d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -68,12 +68,11 @@ /** * MongoDB implementation of the SubmodelRepository * - * @author jungjan, kammognie + * @author jungjan, kammognie, zhangzai, danish * */ public class MongoDBSubmodelRepository implements SubmodelRepository { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); - private static final String ID = "_id"; private static final String MONGO_ID = "_id"; private static final String TEMP_DIR_PREFIX = "basyx-temp"; private static final String GRIDFS_ID_DELIMITER = "#"; @@ -322,12 +321,12 @@ private String resolveCursor(PaginationInfo pRequest, List foundDescripto } private void applySorting(Query query, PaginationInfo pInfo) { - query.with(Sort.by(Direction.ASC, ID)); + query.with(Sort.by(Direction.ASC, MONGO_ID)); } private void applyPagination(Query query, PaginationInfo pInfo) { if (pInfo.getCursor() != null) { - query.addCriteria(Criteria.where(ID).gt(pInfo.getCursor())); + query.addCriteria(Criteria.where(MONGO_ID).gt(pInfo.getCursor())); } if (pInfo.getLimit() != null) { query.limit(pInfo.getLimit()); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index 3b5cdb54f..542282db6 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -38,7 +38,7 @@ /** * SubmodelRepository factory returning a MongoDb backend SubmodelRepository * - * @author jungjan + * @author jungjan, zhangzai * */ @Component @@ -62,17 +62,16 @@ public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${b @Autowired(required = false) public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, - @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { + GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); - this.smRepositoryName = smRepositoryName; this.gridFsTemplate = gridFsTemplate; } @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, - GridFsTemplate gridFsTemplate) { - this(mongoTemplate, collectionName, submodelServiceFactory); - this.gridFsTemplate = gridFsTemplate; + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + GridFsTemplate gridFsTemplate, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName) { + this(mongoTemplate, collectionName, submodelServiceFactory, gridFsTemplate); + this.smRepositoryName = smRepositoryName; } @Autowired(required = false) diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index fbe200ae9..bf0d1947f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -218,7 +218,7 @@ public default String getName() { * @param submodelId * the Submodel id * @param idShortPath - * the IdShort of the file element + * the IdShort path of the file element * @return * * @throws ElementDoesNotExistException @@ -233,7 +233,7 @@ public default String getName() { * @param submodelId * the Submodel id * @param idShortPath - * the IdShort of the file element + * the IdShort path of the file element * @param file * the file object to upload * @@ -248,7 +248,7 @@ public default String getName() { * @param submodelId * the Submodel id * @param idShortPath - * the IdShort of the file element + * the IdShort path of the file element * * @throws ElementDoesNotExistException * @throws ElementNotAFileException diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 1224b7cb8..3a1503dea 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -65,7 +65,7 @@ /** * Testsuite for implementations of the SubmodelRepository interface * - * @author schnicke, danish, kammognie, zhang + * @author schnicke, danish, kammognie, zhangzai * */ public abstract class SubmodelRepositorySuite { @@ -296,6 +296,7 @@ public void getFile(){ } catch (IOException e1) { e1.printStackTrace(); } + try { repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); } catch (IOException e) { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index 2ea09e166..4ba0991a0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -195,12 +195,14 @@ public void uploadFileToFileSubmodelElement() throws IOException { @Test public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); } @Test public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist"); + assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); } @@ -218,36 +220,44 @@ public void deleteFile() throws FileNotFoundException, IOException { @Test public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); } @Test public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); } @Test public void getFile() throws FileNotFoundException, IOException, ParseException { uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); assertEquals(HttpStatus.OK.value(), response.getCode()); + String received = BaSyxHttpTestUtils.getResponseAsString(response); String fileName = "BaSyx-Logo.png"; + assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); } @Test public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); } @Test public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); } @@ -299,12 +309,15 @@ private String createSMEFileUploadURL(String submodelId, String submodelElementI private static String readFile(String path, Charset encoding) throws IOException { byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); } private void assertSubmodelCreationReponse(String submodelJSON, CloseableHttpResponse creationResponse) throws IOException, ParseException, JsonProcessingException, JsonMappingException { assertEquals(HttpStatus.CREATED.value(), creationResponse.getCode()); + String response = BaSyxHttpTestUtils.getResponseAsString(creationResponse); + BaSyxHttpTestUtils.assertSameJSONContent(submodelJSON, response); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java index 99689e9a5..a60d8deda 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java @@ -85,6 +85,7 @@ protected String getURL() { private void createSubmodel4FileTest() throws FileNotFoundException, IOException { String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath(SUBMODEL_JSON); + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); }