From 3597022d917402795f08b5eb2fd450606efd48c5 Mon Sep 17 00:00:00 2001 From: Frank Schnicke Date: Thu, 4 Jan 2024 09:38:02 +0100 Subject: [PATCH] Fixes AASXDeserializer getRelatedFiles crashes * Only file URLs are tried to resolve * Non-existing files lead to a warning Signed-off-by: Frank Schnicke --- dataformat-aasx/pom.xml | 5 ++ .../v3/dataformat/aasx/AASXDeserializer.java | 11 +++- .../v3/dataformat/aasx/AASXSerializer.java | 13 ++-- .../dataformat/aasx/internal/AASXUtils.java | 60 ++++++++++-------- .../deserialization/AASXDeserializerTest.java | 61 ++++++++++++++----- .../aasx/internal/TestAASXUtils.java | 60 ++++++++++++++++++ pom.xml | 4 +- 7 files changed, 161 insertions(+), 53 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..574cb765b 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; } @@ -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..df8d31f7d 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,48 @@ +/* + * 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; +/** + * @author schnicke + */ 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..cd9e0f2ff --- /dev/null +++ b/dataformat-aasx/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/aasx/internal/TestAASXUtils.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * @author schnicke + */ +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 d2b698b2a..f1e486aea 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