diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/pom.xml b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/pom.xml index 2d6a74882..78154f227 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-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 diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/AasInMemoryBackend.java b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/AasInMemoryBackend.java index 42ceba322..1aaa6a7ef 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/AasInMemoryBackend.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/AasInMemoryBackend.java @@ -111,5 +111,7 @@ public void deleteAll(Iterable entities) { public void deleteAll() { inMemoryStore.clear(); } + + } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java index df8d4aa51..490c8abb2 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java @@ -33,7 +33,6 @@ import org.eclipse.digitaltwin.basyx.aasrepository.backend.AasBackendProvider; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.backend.SimpleAasRepositoryFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.AasInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; import org.junit.Test; diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-1.png b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-1.png new file mode 100644 index 000000000..da613e94c Binary files /dev/null and b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-1.png differ diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-2.png b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-2.png new file mode 100644 index 000000000..819b68188 Binary files /dev/null and b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/resources/BaSyx-Logo-2.png differ diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-1.png b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-1.png new file mode 100644 index 000000000..da613e94c Binary files /dev/null and b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-1.png differ diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-2.png b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-2.png new file mode 100644 index 000000000..da613e94c Binary files /dev/null and b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/BaSyx-Logo-2.png differ diff --git a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml index 0fd9d552f..e3938f105 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml @@ -20,5 +20,9 @@ org.springframework.data spring-data-commons + + commons-io + commons-io + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AASThumbnailHandler.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AASThumbnailHandler.java new file mode 100644 index 000000000..a751d9af4 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AASThumbnailHandler.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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.aasrepository.backend; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; + +import org.apache.commons.io.IOUtils; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Resource; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AASThumbnailHandler { + + private static Logger logger = LoggerFactory.getLogger(AASThumbnailHandler.class); + + public static void updateThumbnail(AasRepository aasRepo, String aasId, String contentType, String filePath) { + AssetInformation assetInfor = aasRepo.getAssetInformation(aasId); + assetInfor.getDefaultThumbnail().setContentType(contentType); + assetInfor.getDefaultThumbnail().setPath(filePath); + aasRepo.setAssetInformation(aasId, assetInfor); + } + + public static void setNewThumbnail(AasRepository aasRepo, String aasId, String contentType, String filePath) { + Resource resource = new DefaultResource(); + resource.setContentType(contentType); + resource.setPath(filePath); + AssetInformation assetInfor = aasRepo.getAssetInformation(aasId); + assetInfor.setDefaultThumbnail(resource); + aasRepo.setAssetInformation(aasId, assetInfor); + } + + public static void throwIfFileDoesNotExist(String aasId, Resource resource) { + if (resource == null) + throw new FileDoesNotExistException(aasId); + + String filePath = resource.getPath(); + throwIfFilePathIsNotValid(aasId, filePath); + } + + public static String createFilePath(String tmpDirectory, String aasId, String fileName) { + return tmpDirectory + "/" + aasId + "-" + "Thumbnail" + "-" + fileName; + } + + public static 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); + } + } + + public static void deleteExistingFile(String path) { + if (path == null || path.isEmpty()) + return; + + try { + Files.deleteIfExists(Paths.get(path, "")); + } catch (IOException e) { + logger.error("Unable to delete the file having path '{}'", path); + } + } + + public static String getTemporaryDirectoryPath() { + String tempDirectoryPath = ""; + try { + tempDirectoryPath = Files.createTempDirectory("basyx-temp-thumbnail").toAbsolutePath().toString(); + } catch (IOException e) { + logger.error("Unable to create file in the temporary path."); + } + return tempDirectoryPath; + } + + private static void throwIfFilePathIsNotValid(String aasId, String filePath) { + if (filePath.isEmpty()) + throw new FileDoesNotExistException(aasId); + try { + Paths.get(filePath); + } catch (InvalidPathException | NullPointerException ex) { + throw new FileDoesNotExistException(aasId); + } + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java index 7addf32b0..6f1592450 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java @@ -24,6 +24,8 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository.backend; +import java.io.File; +import java.io.InputStream; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; @@ -32,6 +34,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Resource; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; @@ -45,9 +48,10 @@ import org.springframework.data.repository.CrudRepository; /** - * Default Implementation for the {@link AasRepository} based on Spring {@link CrudRepository} + * Default Implementation for the {@link AasRepository} based on Spring + * {@link CrudRepository} * - * @author mateusmolina, despen + * @author mateusmolina, despen, zhangzai * */ public class CrudAasRepository implements AasRepository { @@ -57,7 +61,7 @@ public class CrudAasRepository implements AasRepository { private AasServiceFactory aasServiceFactory; private String aasRepositoryName = null; - + public CrudAasRepository(AasBackendProvider aasBackendProvider, AasServiceFactory aasServiceFactory) { this.aasBackend = aasBackendProvider.getCrudRepository(); this.aasServiceFactory = aasServiceFactory; @@ -65,7 +69,7 @@ public CrudAasRepository(AasBackendProvider aasBackendProvider, AasServiceFactor public CrudAasRepository(AasBackendProvider aasBackendProvider, AasServiceFactory aasServiceFactory, @Value("${basyx.aasrepo.name:aas-repo}") String aasRepositoryName) { this(aasBackendProvider, aasServiceFactory); - + this.aasRepositoryName = aasRepositoryName; } @@ -147,6 +151,44 @@ public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotE return getAasServiceOrThrow(aasId).getAssetInformation(); } + + @Override + public String getName() { + return aasRepositoryName == null ? AasRepository.super.getName() : aasRepositoryName; + } + + @Override + public File getThumbnail(String aasId) { + Resource resource = getAssetInformation(aasId).getDefaultThumbnail(); + + AASThumbnailHandler.throwIfFileDoesNotExist(aasId, resource); + String filePath = resource.getPath(); + return new File(filePath); + } + + @Override + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + Resource thumbnail = getAssetInformation(aasId).getDefaultThumbnail(); + + if (thumbnail != null) { + updateThumbnailFile(aasId, fileName, contentType, inputStream, thumbnail); + return; + } + + String filePath = createFile(aasId, fileName, inputStream); + AASThumbnailHandler.setNewThumbnail(this, aasId, contentType, filePath); + } + + @Override + public void deleteThumbnail(String aasId) { + Resource thumbnail = getAssetInformation(aasId).getDefaultThumbnail(); + AASThumbnailHandler.throwIfFileDoesNotExist(aasId, thumbnail); + + deleteThumbnailFile(thumbnail); + + updateThumbnailInAssetInformation(aasId); + } + private AasService getAasServiceOrThrow(String aasId) { AssetAdministrationShell aas = aasBackend.findById(aasId).orElseThrow(() -> new ElementDoesNotExistException(aasId)); @@ -170,9 +212,30 @@ private void throwIfAasDoesNotExist(String aasId) { throw new ElementDoesNotExistException(aasId); } - @Override - public String getName() { - return aasRepositoryName == null ? AasRepository.super.getName() : aasRepositoryName; + private void updateThumbnailInAssetInformation(String aasId) { + AssetInformation assetInfor = getAssetInformation(aasId); + assetInfor.getDefaultThumbnail().setContentType(""); + assetInfor.getDefaultThumbnail().setPath(""); + setAssetInformation(aasId, assetInfor); + } + + private void deleteThumbnailFile(Resource thumbnail) { + String filePath = thumbnail.getPath(); + java.io.File tmpFile = new java.io.File(filePath); + tmpFile.delete(); + } + + private void updateThumbnailFile(String aasId, String fileName, String contentType, InputStream inputStream, Resource thumbnail) { + String path = thumbnail.getPath(); + AASThumbnailHandler.deleteExistingFile(path); + String filePath = createFile(aasId, fileName, inputStream); + AASThumbnailHandler.updateThumbnail(this, aasId, contentType, filePath); + } + + private String createFile(String aasId, String fileName, InputStream inputStream) { + String filePath = AASThumbnailHandler.createFilePath(AASThumbnailHandler.getTemporaryDirectoryPath(), aasId, fileName); + AASThumbnailHandler.createFileAtSpecifiedPath(fileName, inputStream, filePath); + return filePath; } } diff --git a/basyx.aasrepository/basyx.aasrepository-core/pom.xml b/basyx.aasrepository/basyx.aasrepository-core/pom.xml index dd9335319..931042ba2 100644 --- a/basyx.aasrepository/basyx.aasrepository-core/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-core/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 @@ -11,6 +12,11 @@ basyx.aasrepository-core + + commons-io + commons-io + test + org.eclipse.digitaltwin.basyx basyx.core diff --git a/basyx.aasrepository/basyx.aasrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepository.java b/basyx.aasrepository/basyx.aasrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepository.java index 3502f0f1b..2c423840c 100644 --- a/basyx.aasrepository/basyx.aasrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepository.java @@ -24,6 +24,8 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository; +import java.io.File; +import java.io.InputStream; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -121,6 +123,37 @@ public interface AasRepository { * @return the requested AAS */ public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException; + + /** + * Get Thumbnail of the specific aas + * + * @param aasID + * the id of the AAS + * @return the file of the thumbnail + */ + public File getThumbnail(String aasId); + + /** + * Set Thumbnail of the AAS + * + * @param aasID + * the id of the AAS + * @param fileName + * name of the thumbnail file with extension + * @param contentType + * content type of the file + * @param inputStream + * inputstream of the thumbnail file + */ + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream); + + /** + * Delete the thumbnail file of the AAS + * + * @param aasId + * the id of the AAS + */ + public void deleteThumbnail(String aasId); /** * Returns the name of the repository diff --git a/basyx.aasrepository/basyx.aasrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepositorySuite.java b/basyx.aasrepository/basyx.aasrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepositorySuite.java index 48e585cb6..b1f6e5fe8 100644 --- a/basyx.aasrepository/basyx.aasrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepositorySuite.java +++ b/basyx.aasrepository/basyx.aasrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/AasRepositorySuite.java @@ -29,11 +29,19 @@ 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.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.apache.commons.io.FileUtils; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.AssetKind; @@ -46,11 +54,13 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.junit.Before; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; /** * Testsuite for implementations of the AasRepository interface @@ -70,6 +80,8 @@ public abstract class AasRepositorySuite { private List preconfiguredShells = new ArrayList<>(); private static final String DUMMY_SUBMODEL_ID = "dummySubmodelId"; + private static final String THUMBNAIL_FILE_PATH_1 = "BaSyx-Logo-1.png"; + private static final String THUMBNAIL_FILE_PATH_2 = "BaSyx-Logo-2.png"; private AasRepository aasRepo; @@ -80,15 +92,12 @@ public abstract class AasRepositorySuite { @Before public void createAasRepoWithDummyAas() { aasRepo = getAasRepository(); - + sanitizeRepository(); - aas1 = new DefaultAssetAdministrationShell.Builder().id(AAS_1_ID) - .submodels(createDummyReference(DUMMY_SUBMODEL_ID)) - .build(); + aas1 = new DefaultAssetAdministrationShell.Builder().id(AAS_1_ID).submodels(createDummyReference(DUMMY_SUBMODEL_ID)).build(); - aas2 = new DefaultAssetAdministrationShell.Builder().id(AAS2) - .build(); + aas2 = new DefaultAssetAdministrationShell.Builder().id(AAS2).build(); AssetInformation assetInfo = createDummyAssetInformation(); aas2.setAssetInformation(assetInfo); @@ -97,7 +106,7 @@ public void createAasRepoWithDummyAas() { preconfiguredShells.forEach(shell -> aasRepo.createAas(shell)); } - + @Test public void getDefaultAasRepositoryName() { assertEquals("aas-repo", aasRepo.getName()); @@ -106,8 +115,7 @@ public void getDefaultAasRepositoryName() { @Test public void allAasRetrieval() throws Exception { PaginationInfo pInfo = new PaginationInfo(2, null); - Collection coll = aasRepo.getAllAas(pInfo) - .getResult(); + Collection coll = aasRepo.getAllAas(pInfo).getResult(); assertEquals(preconfiguredShells, coll); } @@ -147,15 +155,13 @@ public void deleteNonExistingAas() { public void getSubmodelReferences() { Reference reference = createDummyReference(DUMMY_SUBMODEL_ID); - List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo) - .getResult(); + List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo).getResult(); assertTrue(submodelReferences.contains(reference)); } @Test(expected = ElementDoesNotExistException.class) public void getSubmodelReferencesOfNonExistingAas() { - aasRepo.getSubmodelReferences("doesNotMatter", noLimitPaginationInfo) - .getResult(); + aasRepo.getSubmodelReferences("doesNotMatter", noLimitPaginationInfo).getResult(); } @Test @@ -163,8 +169,7 @@ public void addSubmodelReference() { Reference reference = createDummyReference(DUMMY_SUBMODEL_ID); aasRepo.addSubmodelReference(aas1.getId(), reference); - List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo) - .getResult(); + List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo).getResult(); assertTrue(submodelReferences.contains(reference)); } @@ -180,8 +185,7 @@ public void removeSubmodelReference() { Reference reference = createDummyReference(DUMMY_SUBMODEL_ID); aasRepo.removeSubmodelReference(aas1.getId(), DUMMY_SUBMODEL_ID); - List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo) - .getResult(); + List submodelReferences = aasRepo.getSubmodelReferences(aas1.getId(), noLimitPaginationInfo).getResult(); assertFalse(submodelReferences.contains(reference)); } @@ -213,9 +217,7 @@ public void updateNonExistingAas() { @Test(expected = IdentificationMismatchException.class) public void updateExistingAasWithMismatchedIdentifier() { - AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder().id("mismatchId") - .submodels(createDummyReference(DUMMY_SUBMODEL_ID)) - .build(); + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder().id("mismatchId").submodels(createDummyReference(DUMMY_SUBMODEL_ID)).build(); aasRepo.updateAas(AAS_1_ID, aas); } @@ -247,10 +249,7 @@ public void getPaginatedAssetAdministrationShell() { CursorResult> result = aasRepo.getAllAas(new PaginationInfo(1, null)); List resultList = result.getResult(); assertEquals(1, resultList.size()); - assertEquals(AAS_1_ID, resultList.stream() - .findFirst() - .get() - .getId()); + assertEquals(AAS_1_ID, resultList.stream().findFirst().get().getId()); } @Test @@ -261,43 +260,83 @@ public void getPaginatedAssetAdministrationShellIterating() { result = aasRepo.getAllAas(new PaginationInfo(1, cursor)); List resultList = result.getResult(); assertEquals(1, resultList.size()); - assertEquals(AAS2, resultList.stream() - .findFirst() - .get() - .getId()); + assertEquals(AAS2, resultList.stream().findFirst().get().getId()); } @Test public void getPaginatedSubmodelReferencesPaginated() { List submodelReferences = createDummyReferences(); - AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder().id("paginatedAAS") - .submodels(submodelReferences) - .build(); + AssetAdministrationShell aas = new DefaultAssetAdministrationShell.Builder().id("paginatedAAS").submodels(submodelReferences).build(); aasRepo.createAas(aas); PaginationInfo pInfo = new PaginationInfo(1, ""); CursorResult> paginatedReferences = aasRepo.getSubmodelReferences("paginatedAAS", pInfo); - assertEquals(1, paginatedReferences.getResult() - .size()); - assertEquals(submodelReferences.stream() - .findFirst() - .get(), - paginatedReferences.getResult() - .stream() - .findFirst() - .get()); + assertEquals(1, paginatedReferences.getResult().size()); + assertEquals(submodelReferences.stream().findFirst().get(), paginatedReferences.getResult().stream().findFirst().get()); + } + + @Test + public void updateThumbnail() throws FileNotFoundException, IOException { + // Set the thumbnail of the AAS for the first time + aasRepo.setThumbnail(AAS2, THUMBNAIL_FILE_PATH_1, "", getInputStreamOfFileFromClasspath(THUMBNAIL_FILE_PATH_1)); + + // Set the thumbnail of the AAS for the second time with a new figure + aasRepo.setThumbnail(AAS2, THUMBNAIL_FILE_PATH_2, "", getInputStreamOfFileFromClasspath(THUMBNAIL_FILE_PATH_2)); + + File retrievedThumbnail = aasRepo.getThumbnail(AAS2); + + assertEquals(readFile("src/test/resources/" + THUMBNAIL_FILE_PATH_2, Charset.defaultCharset()), new String(FileUtils.readFileToByteArray(retrievedThumbnail), Charset.defaultCharset())); + + } + + @Test + public void setThumbnail() throws FileNotFoundException, IOException { + aasRepo.setThumbnail(AAS2, THUMBNAIL_FILE_PATH_1, "", getInputStreamOfFileFromClasspath(THUMBNAIL_FILE_PATH_1)); + + File retrievedThumbnail = aasRepo.getThumbnail(AAS2); + + assertEquals(readFile("src/test/resources/" + THUMBNAIL_FILE_PATH_1, Charset.defaultCharset()), new String(FileUtils.readFileToByteArray(retrievedThumbnail), Charset.defaultCharset())); + + } + + @Test + public void getThumbnail() throws IOException { + aasRepo.setThumbnail(AAS2, THUMBNAIL_FILE_PATH_1, "", getInputStreamOfFileFromClasspath(THUMBNAIL_FILE_PATH_1)); + + File retrievedThumbnail = aasRepo.getThumbnail(AAS2); + + assertEquals(readFile("src/test/resources/" + THUMBNAIL_FILE_PATH_1, Charset.defaultCharset()), new String(FileUtils.readFileToByteArray(retrievedThumbnail), Charset.defaultCharset())); + } + + @Test(expected = FileDoesNotExistException.class) + public void getNonExistingFile() { + aasRepo.getThumbnail(AAS2); + } + + @Test(expected = FileDoesNotExistException.class) + public void deleteThumbnail() throws FileNotFoundException, IOException { + aasRepo.setThumbnail(AAS2, THUMBNAIL_FILE_PATH_1, "", getInputStreamOfFileFromClasspath(THUMBNAIL_FILE_PATH_1)); + aasRepo.deleteThumbnail(AAS2); + + aasRepo.getThumbnail(AAS2); + } + + @Test(expected = FileDoesNotExistException.class) + public void deleteNonExistingFile() throws IOException { + aasRepo.deleteThumbnail(AAS2); + } + + private static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + + return new String(encoded, encoding); } public static Reference createDummyReference(String submodelId) { - return new DefaultReference.Builder().keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL) - .value(submodelId) - .build()) - .build(); + return new DefaultReference.Builder().keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value(submodelId).build()).build(); } private AssetInformation createDummyAssetInformation() { - return new DefaultAssetInformation.Builder().assetKind(AssetKind.INSTANCE) - .globalAssetId("assetIDTestKey") - .build(); + return new DefaultAssetInformation.Builder().assetKind(AssetKind.INSTANCE).globalAssetId("assetIDTestKey").build(); } /** @@ -306,13 +345,15 @@ private AssetInformation createDummyAssetInformation() { private List createDummyReferences() { List referenceList = new ArrayList<>(); for (int i = 0; i < 5; i++) { - Reference ref = new DefaultReference.Builder().type(ReferenceTypes.MODEL_REFERENCE) - .keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL) - .value("smRef_" + i) - .build()) - .build(); + Reference ref = new DefaultReference.Builder().type(ReferenceTypes.MODEL_REFERENCE).keys(new DefaultKey.Builder().type(KeyTypes.SUBMODEL).value("smRef_" + i).build()).build(); referenceList.add(ref); } return referenceList; } + + private InputStream getInputStreamOfFileFromClasspath(String fileName) throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource(fileName); + + return classPathResource.getInputStream(); + } } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepository.java index e73e6b733..f2d3c90e6 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/MqttAasRepository.java @@ -24,6 +24,8 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository.feature.mqtt; +import java.io.File; +import java.io.InputStream; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.dataformat.SerializationException; @@ -172,4 +174,19 @@ private MqttMessage createMqttMessage(String payload) { } } + @Override + public File getThumbnail(String aasId) { + return decorated.getThumbnail(aasId); + } + + @Override + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + decorated.setThumbnail(aasId, fileName, contentType, inputStream); + } + + @Override + public void deleteThumbnail(String aasId) { + decorated.deleteThumbnail(aasId); + } + } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java index 18c73a895..021daaf4c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java @@ -24,6 +24,8 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository.feature.registry.integration; +import java.io.File; +import java.io.InputStream; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -168,4 +170,19 @@ private boolean shellExistsOnRegistry(String shellId, RegistryAndDiscoveryInterf } } + @Override + public File getThumbnail(String aasId) { + return decorated.getThumbnail(aasId); + } + + @Override + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + decorated.setThumbnail(aasId, fileName, contentType, inputStream); + } + + @Override + public void deleteThumbnail(String aasId) { + decorated.deleteThumbnail(aasId); + } + } diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java index 7268cab43..1436e0366 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryApiHTTPController.java @@ -25,6 +25,10 @@ package org.eclipse.digitaltwin.basyx.aasrepository.http; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Schema; @@ -45,11 +49,14 @@ import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResultPagingMetadata; 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; 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; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @RestController @@ -168,4 +175,46 @@ private String getEncodedCursorFromCursorResult(CursorResult cursorResult) { return Base64UrlEncodedCursor.encodeCursor(cursorResult.getCursor()); } + + @Override + public ResponseEntity deleteThumbnailAasRepository(Base64UrlEncodedIdentifier aasIdentifier) { + aasRepository.deleteThumbnail(aasIdentifier.getIdentifier()); + return new ResponseEntity(HttpStatus.OK); + } + + @Override + public ResponseEntity getThumbnailAasRepository(Base64UrlEncodedIdentifier aasIdentifier) { + try { + FileInputStream fileInputStream = new FileInputStream(aasRepository.getThumbnail(aasIdentifier.getIdentifier())); + Resource resource = new InputStreamResource(fileInputStream); + return new ResponseEntity(resource, HttpStatus.OK); + } catch (FileNotFoundException e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity putThumbnailAasRepository(Base64UrlEncodedIdentifier aasIdentifier, String fileName, @Valid MultipartFile file) { + InputStream fileInputstream = null; + try { + fileInputstream = file.getInputStream(); + aasRepository.setThumbnail(aasIdentifier.getIdentifier(), fileName, file.getContentType(), fileInputstream); + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.OK); + } catch (IOException e) { + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + private void closeInputStream(InputStream fileInputstream) { + if (fileInputstream == null) + return; + + try { + fileInputstream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java index 8ed0b9616..dd138be5c 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPApi.java @@ -51,6 +51,7 @@ import org.eclipse.digitaltwin.basyx.http.model.Result; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.pagination.PagedResult; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; @@ -58,6 +59,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; @jakarta.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-01-10T15:59:05.892Z[GMT]") @Validated @@ -254,4 +257,61 @@ ResponseEntity putAssetInformationAasRepository( @RequestMapping(value = "/shells/{aasIdentifier}/asset-information", produces = { "application/json" }, method = RequestMethod.GET) ResponseEntity getAssetInformationAasRepository( @Parameter(in = ParameterIn.PATH, description = "The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("aasIdentifier") Base64UrlEncodedIdentifier aasIdentifier); + + @Operation(summary = "", description = "", tags = { "Asset Administration Shell Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Thumbnail deletion successful"), + + @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 = "/shells/{aasIdentifier}/asset-information/thumbnail", produces = { "application/json" }, method = RequestMethod.DELETE) + ResponseEntity deleteThumbnailAasRepository( + @Parameter(in = ParameterIn.PATH, description = "The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("aasIdentifier") Base64UrlEncodedIdentifier aasIdentifier); + + @Operation(summary = "", description = "", tags = { "Asset Administration Shell Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "The thumbnail of the Asset Information.", 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 = "/shells/{aasIdentifier}/asset-information/thumbnail", produces = { "application/octet-stream", "application/json" }, method = RequestMethod.GET) + ResponseEntity getThumbnailAasRepository( + @Parameter(in = ParameterIn.PATH, description = "The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("aasIdentifier") Base64UrlEncodedIdentifier aasIdentifier); + + @Operation(summary = "", description = "", tags = { "Asset Administration Shell Repository API" }) + @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "Thumbnail 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 = "/shells/{aasIdentifier}/asset-information/thumbnail", produces = { "application/json" }, consumes = { "multipart/form-data" }, method = RequestMethod.PUT) + ResponseEntity putThumbnailAasRepository( + @Parameter(in = ParameterIn.PATH, description = "The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("aasIdentifier") Base64UrlEncodedIdentifier aasIdentifier, + @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); + } diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPSuite.java b/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPSuite.java index 23e5ce995..c91050f39 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPSuite.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/AasRepositoryHTTPSuite.java @@ -26,13 +26,21 @@ package org.eclipse.digitaltwin.basyx.aasrepository.http; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpPut; +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.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.dataformat.DeserializationException; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursor; @@ -40,7 +48,9 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -53,6 +63,7 @@ */ public abstract class AasRepositoryHTTPSuite { private static final String dummyAasId = "customIdentifier"; + private static final String THUMBNAIL_FILE_PATH = "BaSyx-Logo.png"; private final String CURSOR = "AasNumber3Identifier"; private final String ENCODED_CURSOR = Base64UrlEncodedCursor.encodeCursor(CURSOR); @@ -283,6 +294,64 @@ public void paginationResult() throws FileNotFoundException, IOException, ParseE BaSyxHttpTestUtils.assertSameJSONContent(getPaginatedAas1JSONString(), getJSONWithoutCursorInfo(response)); } + @Test + public void uploadThumbnailToShell() throws IOException { + createDummyAasOnServer(getAas1JSONString()); + CloseableHttpResponse getThumbnailResponse = uploadThumbnail(dummyAasId); + + assertEquals(HttpStatus.OK.value(), getThumbnailResponse.getCode()); + + getThumbnailResponse.close(); + } + + @Test + public void getThumbnail() throws FileNotFoundException, IOException, ParseException { + createDummyAasOnServer(getAas1JSONString()); + + byte[] expectedFile = readBytesFromClasspath(THUMBNAIL_FILE_PATH); + + uploadThumbnail(dummyAasId); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(BaSyxHttpTestUtils.getThumbnailAccessURL(getURL(), dummyAasId)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + byte[] actualFile = EntityUtils.toByteArray(response.getEntity()); + + response.close(); + + assertArrayEquals(expectedFile, actualFile); + } + + @Test + public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(BaSyxHttpTestUtils.getThumbnailAccessURL(getURL(), dummyAasId)); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + + response.close(); + } + + @Test + public void deleteThumbnail() throws FileNotFoundException, IOException { + createDummyAasOnServer(getAas1JSONString()); + uploadThumbnail(dummyAasId); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(BaSyxHttpTestUtils.getThumbnailAccessURL(getURL(), dummyAasId)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + response = BaSyxHttpTestUtils.executeGetOnURL(BaSyxHttpTestUtils.getThumbnailAccessURL(getURL(), dummyAasId)); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + + response.close(); + } + + @Test + public void deleteNonExistingThumbnail() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(BaSyxHttpTestUtils.getThumbnailAccessURL(getURL(), dummyAasId)); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + private String getPaginatedAas1JSONString() throws FileNotFoundException, IOException { return BaSyxHttpTestUtils.readJSONStringFromClasspath("PaginatedAasSimple_1.json"); } @@ -333,6 +402,8 @@ protected String getSpecificAasAccessURL(String aasId) { return getURL() + "/" + Base64UrlEncodedIdentifier.encodeIdentifier(aasId); } + + private void assertAasIsNotOnServer(String aasId) throws IOException { CloseableHttpResponse getResponse = getSpecificAas(aasId); assertEquals(HttpStatus.NOT_FOUND.value(), getResponse.getCode()); @@ -387,4 +458,24 @@ private String getPaginatedSingleSMReferenceJson() throws FileNotFoundException, return BaSyxHttpTestUtils.readJSONStringFromClasspath("PaginatedSingleSMReference.json"); } + private CloseableHttpResponse uploadThumbnail(String aasId) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + java.io.File file = ResourceUtils.getFile("src/test/resources/" + THUMBNAIL_FILE_PATH); + + HttpPut putRequest = BaSyxHttpTestUtils.createPutRequestWithFile(getURL(), aasId, THUMBNAIL_FILE_PATH, file); + + return BaSyxHttpTestUtils.executePutRequest(client, putRequest); + } + + + + private byte[] readBytesFromClasspath(String fileName) throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource(fileName); + InputStream in = classPathResource.getInputStream(); + + return in.readAllBytes(); + } + + } diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/test/resources/BaSyx-Logo.png b/basyx.aasrepository/basyx.aasrepository-http/src/test/resources/BaSyx-Logo.png new file mode 100644 index 000000000..da613e94c Binary files /dev/null and b/basyx.aasrepository/basyx.aasrepository-http/src/test/resources/BaSyx-Logo.png differ 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 index bc1ae701f..f47529d9f 100644 --- 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 @@ -41,7 +41,7 @@ public FileDoesNotExistException(String elementId) { } private static String getMsg(String elementId) { - return "Requested File inside File SubmodelElement with ID : " + elementId + " does not exist"; + return "Requested File inside the Asset administration shell or File SubmodelElement with ID : " + elementId + " does not exist"; } } diff --git a/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java index bd54c3db6..94b7730b6 100644 --- a/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java +++ b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java @@ -38,12 +38,17 @@ import org.apache.hc.client5.http.classic.methods.HttpPatch; import org.apache.hc.client5.http.classic.methods.HttpPost; 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.apache.hc.core5.http.io.entity.StringEntity; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.springframework.core.io.ClassPathResource; import com.fasterxml.jackson.core.JsonProcessingException; @@ -198,6 +203,33 @@ public static String removeCursorFromJSON(String inputJSON) throws JsonMappingEx return mapper.writeValueAsString(rootNode); } + public static CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + public static HttpPut createPutRequestWithFile(String url, String aasId, String fileName, java.io.File file) { + HttpPut putRequest = new HttpPut(getThumbnailAccessURL(url, aasId)); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.addTextBody("fileName", fileName); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + putRequest.setEntity(multipart); + return putRequest; + } + + public static String getThumbnailAccessURL(String url, String aasId) { + return url + "/" + Base64UrlEncodedIdentifier.encodeIdentifier(aasId) + "/asset-information/thumbnail"; + } + private static HttpPatch createPatchRequestWithHeader(String url, String content) { HttpPatch patchRequest = new HttpPatch(url);