Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes AASXDeserializer getRelatedFiles crashes #229

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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