From 8967ad8ae31851c838cd767eb2dbcf236fb9e983 Mon Sep 17 00:00:00 2001 From: Frank Schnicke <77283144+FrankSchnicke@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:06:34 +0100 Subject: [PATCH] Fixes AASXDeserializer getRelatedFiles crashes (#229) * Fixes AASXDeserializer getRelatedFiles crashes * Only file URLs are tried to resolve * Non-existing files lead to a warning * Update AASXDeserializer.java * Update AASXUtils.java * Update TestAASXUtils.java --------- Signed-off-by: Frank Schnicke --- dataformat-aasx/pom.xml | 5 ++ .../v3/dataformat/aasx/AASXDeserializer.java | 13 ++-- .../v3/dataformat/aasx/AASXSerializer.java | 13 ++-- .../dataformat/aasx/internal/AASXUtils.java | 57 +++++++++-------- .../deserialization/AASXDeserializerTest.java | 61 ++++++++++++++----- .../aasx/internal/TestAASXUtils.java | 57 +++++++++++++++++ pom.xml | 4 +- 7 files changed, 156 insertions(+), 54 deletions(-) create mode 100644 dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/TestAASXUtils.java diff --git a/dataformat-aasx/pom.xml b/dataformat-aasx/pom.xml index c42a42903..a55af1615 100644 --- a/dataformat-aasx/pom.xml +++ b/dataformat-aasx/pom.xml @@ -47,5 +47,10 @@ ${project.groupId} dataformat-core + + org.slf4j + slf4j-simple + test + diff --git a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXDeserializer.java b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXDeserializer.java index ab919fb93..069e64cd7 100644 --- a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXDeserializer.java +++ b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/AASXDeserializer.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; @@ -123,10 +124,14 @@ public String getXMLResourceString() throws InvalidFormatException, IOException * if deserialization of the serialized aas environment fails */ public List getRelatedFiles() throws InvalidFormatException, IOException, DeserializationException { - List filePaths = parseReferencedFilePathsFromAASX(); + List filePaths = parseReferencedFilePathsFromAASX().stream().filter(AASXUtils::isFilePath).collect(Collectors.toList()); List files = new ArrayList<>(); for (String filePath : filePaths) { - files.add(readFile(aasxRoot, filePath)); + try { + files.add(readFile(aasxRoot, filePath)); + } catch (Exception e) { + logger.warn("Loading file " + filePath + " failed and will not be included. Exception: " + e); + } } return files; } @@ -163,7 +168,7 @@ private PackageRelationshipCollection getXMLDocumentRelation(PackagePart originP private String getXMLPart(PackagePart originPart) throws InvalidFormatException { if (isCompatibilityModeNeeded(originPart)) { - logger.warn("AASX contains wrong Relationship namespace. This may be related to a bug in AASX Package Explorer or an old version of AAS4J. Future compatibility with the wrong namespace may not be guaranteed"); + logger.warn("AASX contains wrong Relationship namespace. This may be related to the AASX being created with an old version of AASX Package Explorer or an old version of AAS4J. Future compatibility with the wrong namespace may not be guaranteed"); return AASPEC_RELTYPE_BACKWARDSCOMPATIBLE; } else { return AASXSerializer.AASSPEC_RELTYPE; @@ -227,7 +232,7 @@ private List parseElements(Collection elements) { } private InMemoryFile readFile(OPCPackage aasxRoot, String filePath) throws InvalidFormatException, IOException { - PackagePart part = aasxRoot.getPart(PackagingURIHelper.createPartName(AASXUtils.getPathFromURL(filePath))); + PackagePart part = aasxRoot.getPart(PackagingURIHelper.createPartName(AASXUtils.removeFilePartOfURI(filePath))); InputStream stream = part.getInputStream(); return new InMemoryFile(stream.readAllBytes(), filePath); } 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 ebacc682d..f17f5b54f 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 @@ -127,11 +127,11 @@ private void storeFilesInAASX(Environment environment, Collection && aas.getAssetInformation().getDefaultThumbnail() != null && aas.getAssetInformation().getDefaultThumbnail().getPath() != null) .forEach(aas -> createParts(files, - AASXUtils.getPathFromURL(aas.getAssetInformation().getDefaultThumbnail().getPath()), + AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()), rootPackage, xmlPart, aas.getAssetInformation().getDefaultThumbnail().getContentType())); environment.getSubmodels().forEach(sm -> findFileElements(sm.getSubmodelElements()).forEach(file -> createParts(files, - AASXUtils.getPathFromURL(file.getValue()), rootPackage, xmlPart, file.getContentType()))); + AASXUtils.removeFilePartOfURI(file.getValue()), rootPackage, xmlPart, file.getContentType()))); } /** @@ -265,7 +265,7 @@ private void prepareFilePaths(Collection submodels) { */ private InMemoryFile findFileByPath(Collection files, String path) { for (InMemoryFile file : files) { - if (AASXUtils.getPathFromURL(file.getPath()).equals(path)) { + if (AASXUtils.removeFilePartOfURI(file.getPath()).equals(path)) { return file; } } @@ -279,11 +279,10 @@ private InMemoryFile findFileByPath(Collection files, String path) * @return the prepared path */ private String preparePath(String path) { - String newPath = AASXUtils.getPathFromURL(path); - if (!newPath.startsWith("file://")) { - newPath = "file://" + newPath; + if (path.startsWith("/")) { + path = "file://" + path; } - return newPath; + return path; } } \ No newline at end of file diff --git a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/AASXUtils.java b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/AASXUtils.java index 6b3415b3c..710318277 100644 --- a/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/AASXUtils.java +++ b/dataformat-aasx/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/AASXUtils.java @@ -1,40 +1,45 @@ +/* + * Copyright (c) 2024 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.internal; public class AASXUtils { /** - * Gets the path from a URL e.g "http://localhost:8080/path/to/test.file" - * results in "/path/to/test.file" + * Removes the file: or file:// suffix of an URI * - * @param url URL to get the path for - * @return the path from the URL + * @param uri + * URI to remove the file suffix from + * @return the URI without the file suffix */ - public static String getPathFromURL(String url) { - if (url == null) { + public static String removeFilePartOfURI(String uri) { + if (uri == null) { return null; } - if (url.contains("://")) { - - // Find the ":" and and remove the "http://" from the url - int index = url.indexOf(":") + 3; - url = url.substring(index); - - // Find the first "/" from the URL (now without the "http://") and remove - // everything before that - index = url.indexOf("/"); - url = url.substring(index); - - // Recursive call to deal with more than one server parts - // (e.g. basyx://127.0.0.1:6998//https://localhost/test/) - return getPathFromURL(url); - } else { - // Make sure the path has a / at the start - if (!url.startsWith("/")) { - url = "/" + url; - } - return url; + if (uri.startsWith("file://")) { + return uri.replaceFirst("file://", ""); + } else if (uri.startsWith("file:")) { + return uri.replaceFirst("file:", ""); } + + return uri; + } + + public static boolean isFilePath(String uri) { + return uri.startsWith("/") || uri.startsWith("file:") || uri.startsWith("./") || uri.startsWith("../"); } } diff --git a/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/deserialization/AASXDeserializerTest.java b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/deserialization/AASXDeserializerTest.java index 77007d622..c13042781 100644 --- a/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/deserialization/AASXDeserializerTest.java +++ b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/deserialization/AASXDeserializerTest.java @@ -15,6 +15,20 @@ */ package org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.deserialization; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.DeserializationException; @@ -23,31 +37,25 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXSerializer; import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.InMemoryFile; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.AASSimple; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +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.DefaultEnvironment; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultFile; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.xml.sax.SAXException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - public class AASXDeserializerTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @Test - public void testRoundTrip() throws SerializationException, IOException, InvalidFormatException, DeserializationException, ParserConfigurationException, SAXException { - + public void roundTrip() throws SerializationException, IOException, InvalidFormatException, DeserializationException, ParserConfigurationException, SAXException { List fileList = new ArrayList<>(); byte[] operationManualContent = { 0, 1, 2, 3, 4 }; byte[] thumbnail = { 0, 1, 2, 3, 4 }; @@ -56,7 +64,7 @@ public void testRoundTrip() throws SerializationException, IOException, InvalidF fileList.add(inMemoryFile); fileList.add(inMemoryFileThumbnail); - File file = tempFolder.newFile("output.aasx"); + java.io.File file = tempFolder.newFile("output.aasx"); new AASXSerializer().write(AASSimple.createEnvironment(), fileList, new FileOutputStream(file)); @@ -66,4 +74,27 @@ public void testRoundTrip() throws SerializationException, IOException, InvalidF assertEquals(AASSimple.createEnvironment(), deserializer.read()); assertTrue(CollectionUtils.isEqualCollection(fileList, deserializer.getRelatedFiles())); } + + @Test + public void relatedFilesAreOnlyResolvedIfWithinAASX() throws IOException, SerializationException, InvalidFormatException, DeserializationException { + Submodel fileSm = new DefaultSubmodel.Builder().id("doesNotMatter").submodelElements(createFileSubmodelElements()).build(); + Environment env = new DefaultEnvironment.Builder().submodels(fileSm).build(); + + byte[] image = { 0, 1, 2, 3, 4 }; + InMemoryFile inMemoryFile = new InMemoryFile(image, "file:///aasx/internalFile.jpg"); + + java.io.File file = tempFolder.newFile("output.aasx"); + new AASXSerializer().write(env, Collections.singleton(inMemoryFile), new FileOutputStream(file)); + + InputStream in = new FileInputStream(file); + AASXDeserializer deserializer = new AASXDeserializer(in); + + assertEquals(Collections.singletonList(inMemoryFile), deserializer.getRelatedFiles()); + } + + private static List createFileSubmodelElements() { + File internalFile = new DefaultFile.Builder().idShort("internalFile").contentType("image/jpeg").value("file:///aasx/internalFile.jpg").build(); + File externalFile = new DefaultFile.Builder().idShort("externalFile").contentType("image/jpeg").value("http://doesNotMatter.com/image").build(); + return Arrays.asList(internalFile, externalFile); + } } diff --git a/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/TestAASXUtils.java b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/TestAASXUtils.java new file mode 100644 index 000000000..46959ea7f --- /dev/null +++ b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/TestAASXUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class TestAASXUtils { + + @Test + public void isFilePath() { + // Cf. RFC8089 + String[] filePaths = { + "file://a", "file:a", "./b/c", "../b/c/d", "/a" + }; + + String[] notFilePaths = { + "http://admin-shell.io/example", "ftp://admin-shell.io/example" + }; + + for (String filePath : filePaths) { + assertTrue(AASXUtils.isFilePath(filePath)); + } + + for (String filePath : notFilePaths) { + assertFalse(AASXUtils.isFilePath(filePath)); + } + } + + @Test + public void removeFilePartOfURI() { + String[] filePaths = { + "file:///a", "file:/a", "/a" + }; + + for (String filePath : filePaths) { + assertEquals("/a", AASXUtils.removeFilePartOfURI(filePath)); + } + } +} diff --git a/pom.xml b/pom.xml index 4b8994805..c8f6e0b31 100644 --- a/pom.xml +++ b/pom.xml @@ -40,9 +40,9 @@ 1 0 0 - -milestone-04 + -SNAPSHOT ${revision.major}.${revision.minor}.${revision.patch}${revision.suffix} - 1.0.0-milestone-04 + 1.0.0-SNAPSHOT UTF-8 UTF-8