Skip to content

Commit

Permalink
Fixes AASXDeserializer getRelatedFiles crashes (#229)
Browse files Browse the repository at this point in the history
* 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 <frank.schnicke@iese.fraunhofer.de>
  • Loading branch information
FrankSchnicke authored Jan 23, 2024
1 parent 072b0f9 commit 8967ad8
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 54 deletions.
5 changes: 5 additions & 0 deletions dataformat-aasx/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,10 @@
<groupId>${project.groupId}</groupId>
<artifactId>dataformat-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -123,10 +124,14 @@ public String getXMLResourceString() throws InvalidFormatException, IOException
* if deserialization of the serialized aas environment fails
*/
public List<InMemoryFile> getRelatedFiles() throws InvalidFormatException, IOException, DeserializationException {
List<String> filePaths = parseReferencedFilePathsFromAASX();
List<String> filePaths = parseReferencedFilePathsFromAASX().stream().filter(AASXUtils::isFilePath).collect(Collectors.toList());
List<InMemoryFile> 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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -227,7 +232,7 @@ private List<String> parseElements(Collection<SubmodelElement> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ private void storeFilesInAASX(Environment environment, Collection<InMemoryFile>
&& 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())));
}

/**
Expand Down Expand Up @@ -265,7 +265,7 @@ private void prepareFilePaths(Collection<Submodel> submodels) {
*/
private InMemoryFile findFileByPath(Collection<InMemoryFile> files, String path) {
for (InMemoryFile file : files) {
if (AASXUtils.getPathFromURL(file.getPath()).equals(path)) {
if (AASXUtils.removeFilePartOfURI(file.getPath()).equals(path)) {
return file;
}
}
Expand All @@ -279,11 +279,10 @@ private InMemoryFile findFileByPath(Collection<InMemoryFile> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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("../");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<InMemoryFile> fileList = new ArrayList<>();
byte[] operationManualContent = { 0, 1, 2, 3, 4 };
byte[] thumbnail = { 0, 1, 2, 3, 4 };
Expand All @@ -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));

Expand All @@ -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<SubmodelElement> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
<revision.major>1</revision.major>
<revision.minor>0</revision.minor>
<revision.patch>0</revision.patch>
<revision.suffix>-milestone-04</revision.suffix>
<revision.suffix>-SNAPSHOT</revision.suffix>
<revision>${revision.major}.${revision.minor}.${revision.patch}${revision.suffix}</revision>
<model.version>1.0.0-milestone-04</model.version>
<model.version>1.0.0-SNAPSHOT</model.version>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
Expand Down

0 comments on commit 8967ad8

Please sign in to comment.