diff --git a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXSerializer.java b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXSerializer.java index 4d2d9e40..c38dd322 100644 --- a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXSerializer.java +++ b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXSerializer.java @@ -50,6 +50,7 @@ public class AASXSerializer { private static final String MIME_PLAINTXT = "text/plain"; private static final String MIME_XML = "application/xml"; + public static final String OPC_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships"; public static final String AASX_NAMESPACE = "http://admin-shell.io/aasx/relationships"; public static final String ORIGIN_RELTYPE = AASX_NAMESPACE + "/aasx-origin"; @@ -61,6 +62,8 @@ public class AASXSerializer { public static final String AASSUPPL_RELTYPE = AASX_NAMESPACE + "/aas-suppl"; + public static final String AAS_THUMBNAIL_RELTYPE = OPC_NAMESPACE + "/metadata/thumbnail"; + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final XmlSerializer xmlSerializer; @@ -74,7 +77,7 @@ public AASXSerializer() { /** * Constructor with a custom serializer for serializing the aas environment - * + * * @param xmlSerializer a custom serializer used for serializing the aas environment */ public AASXSerializer(XmlSerializer xmlSerializer) { @@ -83,7 +86,7 @@ public AASXSerializer(XmlSerializer xmlSerializer) { /** * Generates the .aasx file and writes it to the given OutputStream - * + * * @param environment the aas environment that will be included in the aasx package as an xml serialization * @param files related inMemory files that belong to the given aas environment * @param os an output stream for writing the aasx package @@ -106,6 +109,12 @@ public void write(Environment environment, Collection files, Outpu // Save the XML to aasx/xml/content.xml PackagePart xmlPart = createAASXPart(rootPackage, origin, XML_PATH, MIME_XML, AASSPEC_RELTYPE, xml.getBytes(DEFAULT_CHARSET)); + environment.getAssetAdministrationShells().stream().filter(aas -> aas.getAssetInformation() != null + && aas.getAssetInformation().getDefaultThumbnail() != null + && aas.getAssetInformation().getDefaultThumbnail().getPath() != null) + .forEach(aas -> createParts(files, + AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()), + rootPackage, rootPackage, aas.getAssetInformation().getDefaultThumbnail().getContentType(), AAS_THUMBNAIL_RELTYPE)); storeFilesInAASX(environment, files, rootPackage, xmlPart); saveAASX(os, rootPackage); @@ -113,7 +122,7 @@ public void write(Environment environment, Collection files, Outpu /** * Stores the files from the Submodels in the .aasx file - * + * * @param environment the Environment * @param files the content of the files * @param rootPackage the OPCPackage @@ -121,14 +130,8 @@ public void write(Environment environment, Collection files, Outpu */ private void storeFilesInAASX(Environment environment, Collection files, OPCPackage rootPackage, PackagePart xmlPart) { - environment.getAssetAdministrationShells().stream().filter(aas -> aas.getAssetInformation() != null - && aas.getAssetInformation().getDefaultThumbnail() != null - && aas.getAssetInformation().getDefaultThumbnail().getPath() != null) - .forEach(aas -> createParts(files, - AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()), - rootPackage, xmlPart, aas.getAssetInformation().getDefaultThumbnail().getContentType())); findFileElements(environment).forEach(file -> createParts(files, - AASXUtils.removeFilePartOfURI(file.getValue()), rootPackage, xmlPart, file.getContentType())); + AASXUtils.removeFilePartOfURI(file.getValue()), rootPackage, xmlPart, file.getContentType(), AASSUPPL_RELTYPE)); } /** @@ -139,13 +142,14 @@ private void storeFilesInAASX(Environment environment, Collection * @param rootPackage the OPCPackage * @param xmlPart the Part the files should be related to * @param contentType the contentType of the file + * @param relType the relationship type */ private void createParts(Collection files, String filePath, OPCPackage rootPackage, - PackagePart xmlPart, String contentType) { + RelationshipSource xmlPart, String contentType, String relType) { try { InMemoryFile content = findFileByPath(files, filePath); logger.trace("Writing file '{}' to .aasx.", filePath); - createAASXPart(rootPackage, xmlPart, filePath, contentType, AASSUPPL_RELTYPE, content.getFileContent()); + createAASXPart(rootPackage, xmlPart, filePath, contentType, relType, content.getFileContent()); } catch (RuntimeException e) { // Log that a file is missing and continue building the .aasx logger.warn("Could not add File '{}'. It was not contained in given InMemoryFiles.", filePath, e); @@ -154,7 +158,7 @@ private void createParts(Collection files, String filePath, OPCPac /** * Saves the OPCPackage to the given OutputStream - * + * * @param os the Stream to be saved to * @param rootPackage the Package to be saved * @throws IOException if creating output streams for aasx fails @@ -167,7 +171,7 @@ private void saveAASX(OutputStream os, OPCPackage rootPackage) throws IOExceptio /** * Generates a UUID. Every element of the .aasx needs a unique Id according to * the specification - * + * * @return UUID */ private String createUniqueID() { @@ -177,7 +181,7 @@ private String createUniqueID() { /** * Creates a Part (a file in the .aasx) of the .aasx and adds it to the Package - * + * * @param root the OPCPackage * @param relateTo the Part of the OPC the relationship of the new Part should be added to * @param path the path inside the .aasx where the new Part should be created @@ -209,7 +213,7 @@ private PackagePart createAASXPart(OPCPackage root, RelationshipSource relateTo, /** * Writes the content of a byte[] to a Part - * + * * @param part the Part to be written to * @param content the content to be written to the part */ @@ -225,7 +229,7 @@ private void writeDataToPart(PackagePart part, byte[] content) { /** * Gets the File elements from an environment * searches in SubmodelElementCollections - * + * * @param environment the Environment * @return the found Files */ @@ -244,7 +248,7 @@ public void visit(File file) { /** * Replaces the path in all File Elements with the result of preparePath - * + * * @param environment the Environment */ private void prepareFilePaths(Environment environment) { @@ -253,7 +257,7 @@ private void prepareFilePaths(Environment environment) { /** * Finds an InMemoryFile by its path - * + * * @param files the InMemoryFiles * @param path the path of the wanted file * @return the InMemoryFile if it was found; else null @@ -269,7 +273,7 @@ private InMemoryFile findFileByPath(Collection files, String path) /** * Removes the serverpart from a path and ensures it starts with "file://" - * + * * @param path the path to be prepared * @return the prepared path */ diff --git a/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/serialization/AASXSerializerTest.java b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/serialization/AASXSerializerTest.java index ebbe0c64..2e258efd 100644 --- a/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/serialization/AASXSerializerTest.java +++ b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/serialization/AASXSerializerTest.java @@ -28,8 +28,10 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -38,6 +40,7 @@ public class AASXSerializerTest { + private static final String RELS_PATH_URI = "file:///_rels/.rels"; private static final String XML_PATH_URI = "file:///aasx/xml/content.xml"; private static final String ORIGIN_PATH_URI = "file:///aasx/aasx-origin"; @@ -56,7 +59,7 @@ public void testBuildAASXFull() throws IOException, TransformerException, Parser new AASXSerializer().write(AASFull.createEnvironment(), fileList, out); - validateAASX(out); + validateAASX(out, List.of(AASXSerializerTest::assertRootXml)); } @Test @@ -75,28 +78,26 @@ public void testBuildAASXSimple() throws IOException, TransformerException, Pars new AASXSerializer().write(AASSimple.createEnvironment(), fileList, out); - validateAASX(out); + validateAASX(out, List.of(AASXSerializerTest::assertRootXml, AASXSerializerTest::assertThumbnailReference)); } - private void validateAASX(ByteArrayOutputStream byteStream) throws IOException { + private void validateAASX(ByteArrayOutputStream byteStream, List> fileValidators) { ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(byteStream.toByteArray())); - ZipEntry zipEntry = null; + ZipEntry zipEntry; ArrayList filePaths = new ArrayList<>(); - while ((zipEntry = in.getNextEntry()) != null) { - if (zipEntry.getName().equals(XML_PATH_URI)) { - - // Read the first 5 bytes of the XML file to make sure it is in fact XML file - // No further test of XML file necessary as XML-Converter is tested separately - byte[] buf = new byte[5]; - in.read(buf); - assertEquals(" validator : fileValidators) { + validator.accept(zipEntry, in); + } + // Write the paths of all files contained in the .aasx into filePaths + filePaths.add("file:///" + zipEntry.getName()); } - - // Write the paths of all files contained in the .aasx into filePaths - filePaths.add("file:///" + zipEntry.getName()); + } catch (IOException e) { + throw new RuntimeException(e); } assertTrue(filePaths.contains(XML_PATH_URI)); @@ -110,4 +111,33 @@ private void validateAASX(ByteArrayOutputStream byteStream) throws IOException { } } + + private static void assertRootXml(ZipEntry zipEntry, ZipInputStream in) { + if (!XML_PATH_URI.endsWith(zipEntry.getName())) { + return; + } + // Read the first 5 bytes of the XML file to make sure it is in fact XML file + // No further test of XML file necessary as XML-Converter is tested separately + byte[] buf = new byte[5]; + try { + in.read(buf); + } catch (IOException e) { + throw new RuntimeException(e); + } + assertEquals("