Skip to content

Commit

Permalink
[Bugfix] Files in SubmodelElementLists and NullPointerException when …
Browse files Browse the repository at this point in the history
…serializing/deserializing empty files (#250)

* visit all elements (also list) and add null check

* skip empty files when serializing AASX

* add tests for empty files and files in submodelElementList

* fix null test

* change serializer test to AASFull

provokes java.lang.NullPointerException: Cannot invoke "String.startsWith(String)" because "path" is null

* fix serializer test, add thumbnail to AASFull

* fix missing import

* revert AASFull change, change AASXSerializerTest

* revert unused import

* remove unused imports
  • Loading branch information
fvolz authored Feb 27, 2024
1 parent 0eaa801 commit 6c2b04e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

Expand All @@ -33,11 +32,10 @@
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.internal.AASXUtils;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.visitor.AssetAdministrationShellElementWalkerVisitor;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlDeserializer;
import org.eclipse.digitaltwin.aas4j.v3.model.Environment;
import org.eclipse.digitaltwin.aas4j.v3.model.File;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -207,27 +205,14 @@ private List<String> parseReferencedFilePathsFromAASX() throws IOException, Inva
&& aas.getAssetInformation().getDefaultThumbnail() != null
&& aas.getAssetInformation().getDefaultThumbnail().getPath() != null)
.forEach(aas -> paths.add(aas.getAssetInformation().getDefaultThumbnail().getPath()));
environment.getSubmodels().forEach(sm -> paths.addAll(parseElements(sm.getSubmodelElements())));
return paths;
}

/**
* Gets the file paths from a collection of ISubmodelElement
*
* @param elements the submodel elements to process
* @return the Paths from the File elements
*/
private List<String> parseElements(Collection<SubmodelElement> elements) {
List<String> paths = new ArrayList<>();
for (SubmodelElement element : elements) {
if (element instanceof File) {
File file = (File) element;
paths.add(file.getValue());
} else if (element instanceof SubmodelElementCollection) {
SubmodelElementCollection collection = (SubmodelElementCollection) element;
paths.addAll(parseElements(collection.getValue()));
new AssetAdministrationShellElementWalkerVisitor() {
@Override
public void visit(File file) {
if(file != null && file.getValue() != null) {
paths.add(file.getValue());
}
}
}
}.visit(environment);
return paths;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@
import org.apache.poi.openxml4j.opc.internal.MemoryPackagePart;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.internal.AASXUtils;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.visitor.AssetAdministrationShellElementWalkerVisitor;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlSerializer;
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.SubmodelElementCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -94,7 +92,7 @@ public AASXSerializer(XmlSerializer xmlSerializer) {
*/
public void write(Environment environment, Collection<InMemoryFile> files, OutputStream os)
throws SerializationException, IOException {
prepareFilePaths(environment.getSubmodels());
prepareFilePaths(environment);

OPCPackage rootPackage = OPCPackage.create(os);

Expand Down Expand Up @@ -129,9 +127,8 @@ private void storeFilesInAASX(Environment environment, Collection<InMemoryFile>
.forEach(aas -> createParts(files,
AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()),
rootPackage, xmlPart, aas.getAssetInformation().getDefaultThumbnail().getContentType()));
environment.getSubmodels().forEach(sm ->
findFileElements(sm.getSubmodelElements()).forEach(file -> createParts(files,
AASXUtils.removeFilePartOfURI(file.getValue()), rootPackage, xmlPart, file.getContentType())));
findFileElements(environment).forEach(file -> createParts(files,
AASXUtils.removeFilePartOfURI(file.getValue()), rootPackage, xmlPart, file.getContentType()));
}

/**
Expand Down Expand Up @@ -226,35 +223,32 @@ private void writeDataToPart(PackagePart part, byte[] content) {
}

/**
* Gets the File elements from a collection of elements Also recursively
* Gets the File elements from an environment
* searches in SubmodelElementCollections
*
* @param elements the Elements to be searched for File elements
* @param environment the Environment
* @return the found Files
*/
private Collection<File> findFileElements(Collection<SubmodelElement> elements) {
private Collection<File> findFileElements(Environment environment) {
Collection<File> files = new ArrayList<>();

for (SubmodelElement element : elements) {
if (element instanceof File) {
files.add((File) element);
} else if (element instanceof SubmodelElementCollection) {
// Recursive call to deal with SubmodelElementCollections
files.addAll(findFileElements(((SubmodelElementCollection) element).getValue()));
new AssetAdministrationShellElementWalkerVisitor() {
@Override
public void visit(File file) {
if(file != null && file.getValue() != null) {
files.add(file);
}
}
}

}.visit(environment);
return files;
}

/**
* Replaces the path in all File Elements with the result of preparePath
*
* @param submodels the Submodels
* @param environment the Environment
*/
private void prepareFilePaths(Collection<Submodel> submodels) {
submodels.stream()
.forEach(sm -> findFileElements(sm.getSubmodelElements()).stream().forEach(f -> f.setValue(preparePath(f.getValue()))));
private void prepareFilePaths(Environment environment) {
findFileElements(environment).forEach(f -> f.setValue(preparePath(f.getValue())));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
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.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
Expand Down Expand Up @@ -92,6 +93,37 @@ public void relatedFilesAreOnlyResolvedIfWithinAASX() throws IOException, Serial
assertEquals(Collections.singletonList(inMemoryFile), deserializer.getRelatedFiles());
}

@Test
public void emptyFiles() throws IOException, SerializationException, InvalidFormatException, DeserializationException {
File emptyFile = new DefaultFile.Builder().idShort("emptyFile").contentType(null).value(null).build();
Submodel fileSm = new DefaultSubmodel.Builder().id("doesNotMatter").submodelElements(emptyFile).build();
Environment env = new DefaultEnvironment.Builder().submodels(fileSm).build();

java.io.File file = tempFolder.newFile("output.aasx");
new AASXSerializer().write(env, null, new FileOutputStream(file));

InputStream in = new FileInputStream(file);
AASXDeserializer deserializer = new AASXDeserializer(in);
assertTrue(deserializer.getRelatedFiles().isEmpty());
}

@Test
public void filesInElementList() throws IOException, SerializationException, InvalidFormatException, DeserializationException {
DefaultSubmodelElementList elementList = new DefaultSubmodelElementList.Builder().value(createFileSubmodelElements()).build();
Submodel fileSm = new DefaultSubmodel.Builder().id("doesNotMatter").submodelElements(elementList).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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

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.AASFull;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.AASSimple;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException;
import org.junit.Before;
import org.junit.Test;

import javax.xml.parsers.ParserConfigurationException;
Expand All @@ -43,19 +43,30 @@ public class AASXSerializerTest {

private List<InMemoryFile> fileList = new ArrayList<>();

@Before
public void setup() throws IOException {
@Test
public void testBuildAASXFull() throws IOException, TransformerException, ParserConfigurationException, SerializationException {
byte[] operationManualContent = { 0, 1, 2, 3, 4 };
InMemoryFile file = new InMemoryFile(operationManualContent, "file:///TestFile.pdf");
fileList.add(file);
// This stream can be used to write the .aasx directly to a file
// FileOutputStream out = new FileOutputStream("path/to/test.aasx");

// This stream keeps the output of the AASXFactory only in memory
ByteArrayOutputStream out = new ByteArrayOutputStream();

new AASXSerializer().write(AASFull.createEnvironment(), fileList, out);

validateAASX(out);
}

@Test
public void testBuildAASXSimple() throws IOException, TransformerException, ParserConfigurationException, SerializationException {
byte[] thumbnail = { 0, 1, 2, 3, 4 };
byte[] operationManualContent = { 0, 1, 2, 3, 4 };
InMemoryFile file = new InMemoryFile(operationManualContent, "file:///aasx/OperatingManual.pdf");
InMemoryFile inMemoryFileThumbnail = new InMemoryFile(thumbnail, "file:///master/verwaltungsschale-detail-part1.png");
fileList.add(file);
fileList.add(inMemoryFileThumbnail);
}

@Test
public void testBuildAASX() throws IOException, TransformerException, ParserConfigurationException, SerializationException {

// This stream can be used to write the .aasx directly to a file
// FileOutputStream out = new FileOutputStream("path/to/test.aasx");

Expand Down

0 comments on commit 6c2b04e

Please sign in to comment.