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

GH-287: Add JSON support for serializing and deserializing AASX files #288

Merged
merged 2 commits into from
Apr 17, 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
4 changes: 4 additions & 0 deletions dataformat-aasx/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<groupId>${project.groupId}</groupId>
<artifactId>aas4j-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>aas4j-dataformat-json</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>aas4j-dataformat-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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.json.JsonDeserializer;
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;
Expand All @@ -52,7 +53,8 @@ public class AASXDeserializer {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;


private final XmlDeserializer deserializer;
private final XmlDeserializer xmlDeserializer;
private final JsonDeserializer jsonDeserializer;

private Environment environment;
private final OPCPackage aasxRoot;
Expand All @@ -66,20 +68,40 @@ public class AASXDeserializer {
*/
public AASXDeserializer(InputStream inputStream) throws InvalidFormatException, IOException {
aasxRoot = OPCPackage.open(inputStream);
this.deserializer = new XmlDeserializer();
this.xmlDeserializer = new XmlDeserializer();
this.jsonDeserializer = new JsonDeserializer();
}

/**
* Constructor for custom XML deserialization
*
* @param deserializer a custom deserializer used for deserializing the aas environment
* Constructor for custom deserialization
*
* @param xmlDeserializer a custom XML deserializer used for deserializing the aas environment
* @param inputStream an input stream to an aasx package that can be read with this instance
* @throws InvalidFormatException if aasx package format is invalid
* @throws IOException if creating input streams for aasx fails
*/
public AASXDeserializer(XmlDeserializer xmlDeserializer,
InputStream inputStream) throws InvalidFormatException, IOException {
aasxRoot = OPCPackage.open(inputStream);
this.xmlDeserializer = xmlDeserializer;
this.jsonDeserializer = new JsonDeserializer();
}

/**
* Constructor for custom deserialization
*
* @param xmlDeserializer a custom XML deserializer used for deserializing the aas environment
* @param jsonDeserializer a custom JSON deserializer used for deserializing the aas environment
* @param inputStream an input stream to an aasx package that can be read with this instance
* @throws InvalidFormatException if aasx package format is invalid
* @throws IOException if creating input streams for aasx fails
*/
public AASXDeserializer(XmlDeserializer deserializer, InputStream inputStream) throws InvalidFormatException, IOException {
public AASXDeserializer(XmlDeserializer xmlDeserializer,
tobiaskraft marked this conversation as resolved.
Show resolved Hide resolved
JsonDeserializer jsonDeserializer,
InputStream inputStream) throws InvalidFormatException, IOException {
aasxRoot = OPCPackage.open(inputStream);
this.deserializer = deserializer;
this.xmlDeserializer = xmlDeserializer;
this.jsonDeserializer = jsonDeserializer;
}

/**
Expand All @@ -96,18 +118,61 @@ public Environment read() throws InvalidFormatException, IOException, Deserializ
if (environment != null) {
return environment;
}
environment = deserializer.read(getXMLResourceString(aasxRoot));
if (MetamodelContentType.XML.equals(getContentType())) {
environment = xmlDeserializer.read(getResourceString(aasxRoot));
}
if (MetamodelContentType.JSON.equals(getContentType())) {
environment = jsonDeserializer.read(getResourceString(aasxRoot), Environment.class);
}
return environment;
}

/**
* Currently XML and JSON are supported for deserializing.
* @return The content type of the metafile
* @throws InvalidFormatException if aasx package format is invalid
* @throws IOException if creating input streams for aasx fails
*/
protected MetamodelContentType getContentType() throws InvalidFormatException, IOException {
MetamodelContentType contentType;
PackagePart packagePart = getPackagePart(aasxRoot);
// We also check for the none official content types "test/xml" and "text/json", which are commonly used
switch (packagePart.getContentType()) {
case "text/xml":
case "application/xml":
contentType = MetamodelContentType.XML;
break;
case "text/json":
case "application/json":
contentType = MetamodelContentType.JSON;
break;
default:
throw new RuntimeException("The following content type is not supported: " + packagePart.getContentType());
}
return contentType;
}

/**
* Return the Content of the xml file in the aasx-package as String
*
*
* @deprecated This method will be replaced by the method {@link AASXDeserializer#getResourceString()}.
*
* @throws InvalidFormatException if aasx package format is invalid
* @throws IOException if creating input streams for aasx fails
*/
@Deprecated
public String getXMLResourceString() throws InvalidFormatException, IOException {
return getXMLResourceString(this.aasxRoot);
return getResourceString(this.aasxRoot);
}

/**
* Return the Content of the xml or json file in the aasx-package as String
*
* @throws InvalidFormatException if aasx package format is invalid
* @throws IOException if creating input streams for aasx fails
*/
public String getResourceString() throws InvalidFormatException, IOException {
tobiaskraft marked this conversation as resolved.
Show resolved Hide resolved
return getResourceString(this.aasxRoot);
}

/**
Expand All @@ -134,13 +199,14 @@ public List<InMemoryFile> getRelatedFiles() throws InvalidFormatException, IOExc
return files;
}

private String getXMLResourceString(OPCPackage aasxPackage) throws InvalidFormatException, IOException {
private PackagePart getPackagePart(OPCPackage aasxPackage) throws InvalidFormatException, IOException {
PackagePart originPart = getOriginPart(aasxPackage);

PackageRelationshipCollection originRelationships = getXMLDocumentRelation(originPart);
return originPart.getRelatedPart(originRelationships.getRelationship(0));
}

PackagePart xmlPart = originPart.getRelatedPart(originRelationships.getRelationship(0));

private String getResourceString(OPCPackage aasxPackage) throws InvalidFormatException, IOException {
PackagePart xmlPart = getPackagePart(aasxPackage);
return readContentFromPackagePart(xmlPart);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
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.json.JsonSerializer;
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;
Expand All @@ -49,6 +50,7 @@ public class AASXSerializer {

private static final String MIME_PLAINTXT = "text/plain";
private static final String MIME_XML = "application/xml";
private static final String MIME_JSON = "application/json";

public static final String OPC_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships";
public static final String AASX_NAMESPACE = "http://admin-shell.io/aasx/relationships";
Expand All @@ -59,6 +61,7 @@ public class AASXSerializer {

public static final String AASSPEC_RELTYPE = AASX_NAMESPACE + "/aas-spec";
public static final String XML_PATH = "/aasx/xml/content.xml";
public static final String JSON_PATH = "/aasx/json/content.json";

public static final String AASSUPPL_RELTYPE = AASX_NAMESPACE + "/aas-suppl";

Expand All @@ -67,25 +70,39 @@ public class AASXSerializer {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

private final XmlSerializer xmlSerializer;
private final JsonSerializer jsonSerializer;

/**
* Default constructor
*/
public AASXSerializer() {
this.xmlSerializer = new XmlSerializer();
this.jsonSerializer = new JsonSerializer();
}

/**
* Constructor with a custom serializer for serializing the aas environment
* Constructor with a custom XML serializer for serializing the aas environment
*
* @param xmlSerializer a custom serializer used for serializing the aas environment
* @param xmlSerializer a custom serializer used for serializing the aas environment in XML
*/
public AASXSerializer(XmlSerializer xmlSerializer) {
this.xmlSerializer = xmlSerializer;
this.jsonSerializer = new JsonSerializer();
}

/**
* Generates the .aasx file and writes it to the given OutputStream
* Constructor with custom serializers for serializing the aas environment
*
* @param xmlSerializer a custom serializer used for serializing the aas environment in XML
* @param jsonSerializer a custom serializer used for serializing the aas environment in JSON
*/
public AASXSerializer(XmlSerializer xmlSerializer, JsonSerializer jsonSerializer) {
tobiaskraft marked this conversation as resolved.
Show resolved Hide resolved
this.xmlSerializer = xmlSerializer;
this.jsonSerializer = jsonSerializer;
}

/**
* Generates the .aasx file and writes it to the given OutputStream, by using XML as the default content type.
*
* @param environment the aas environment that will be included in the aasx package as an xml serialization
* @param files related inMemory files that belong to the given aas environment
Expand All @@ -96,25 +113,53 @@ public AASXSerializer(XmlSerializer xmlSerializer) {
public void write(Environment environment, Collection<InMemoryFile> files, OutputStream os)
throws SerializationException, IOException {

write(environment, files, os, MetamodelContentType.XML);
}

/**
* Generates the .aasx file and writes it to the given OutputStream
*
* @param environment the aas environment that will be included in the aasx package as an xml serialization
* @param files related inMemory files that belong to the given aas environment
* @param os an output stream for writing the aasx package
* @param contentType the content type for the metamodel serialization
* @throws SerializationException if serializing the given elements fails
* @throws IOException if creating output streams for aasx fails
*/
public void write(Environment environment, Collection<InMemoryFile> files, OutputStream os, MetamodelContentType contentType)
throws SerializationException, IOException {

OPCPackage rootPackage = OPCPackage.create(os);

// Create the empty aasx-origin file
PackagePart origin = createAASXPart(rootPackage, rootPackage, ORIGIN_PATH, MIME_PLAINTXT, ORIGIN_RELTYPE,
ORIGIN_CONTENT.getBytes());

// Convert the given Metamodels to XML
String xml = xmlSerializer.write(environment);

// Save the XML to aasx/xml/content.xml
PackagePart xmlPart = createAASXPart(rootPackage, origin, XML_PATH, MIME_XML, AASSPEC_RELTYPE, xml.getBytes(DEFAULT_CHARSET));
PackagePart packagePart;
switch (contentType) {
case JSON:
// Convert the given Metamodels to JSON
String json = jsonSerializer.write(environment);
// Save the JSON to aasx/json/content.json
packagePart = createAASXPart(rootPackage, origin, JSON_PATH, MIME_JSON, AASSPEC_RELTYPE, json.getBytes(DEFAULT_CHARSET));
break;
case XML:
// Convert the given Metamodels to XML
String xml = xmlSerializer.write(environment);
// Save the XML to aasx/xml/content.xml
packagePart = createAASXPart(rootPackage, origin, XML_PATH, MIME_XML, AASSPEC_RELTYPE, xml.getBytes(DEFAULT_CHARSET));
break;
default:
throw new IllegalArgumentException("Unsupported content type: " + contentType);
}

environment.getAssetAdministrationShells().stream().filter(aas -> aas.getAssetInformation() != null
&& aas.getAssetInformation().getDefaultThumbnail() != null
&& aas.getAssetInformation().getDefaultThumbnail().getPath() != null)
.forEach(aas -> createParts(files,
AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()),
rootPackage, rootPackage, aas.getAssetInformation().getDefaultThumbnail().getContentType(), AAS_THUMBNAIL_RELTYPE));
storeFilesInAASX(environment, files, rootPackage, xmlPart);
&& aas.getAssetInformation().getDefaultThumbnail() != null
&& aas.getAssetInformation().getDefaultThumbnail().getPath() != null)
.forEach(aas -> createParts(files,
AASXUtils.removeFilePartOfURI(aas.getAssetInformation().getDefaultThumbnail().getPath()),
rootPackage, rootPackage, aas.getAssetInformation().getDefaultThumbnail().getContentType(), AAS_THUMBNAIL_RELTYPE));
storeFilesInAASX(environment, files, rootPackage, packagePart);

saveAASX(os, rootPackage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSchemaValidator;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlSchemaValidator;
import org.xml.sax.SAXException;

Expand All @@ -29,10 +31,12 @@
public class AASXValidator {

private XmlSchemaValidator xmlValidator;
private JsonSchemaValidator jsonValidator;
private AASXDeserializer deserializer;

public AASXValidator(InputStream is) throws SAXException, IOException, InvalidFormatException {
this.xmlValidator = new XmlSchemaValidator();
this.jsonValidator = new JsonSchemaValidator();
this.deserializer = new AASXDeserializer(is);
}

Expand All @@ -44,8 +48,15 @@ public AASXValidator(InputStream is) throws SAXException, IOException, InvalidFo
* @throws InvalidFormatException specified URI is invalid
*/
public Set<String> validateSchema() throws IOException, InvalidFormatException {
String file = deserializer.getXMLResourceString();
return xmlValidator.validateSchema(file);
String file = deserializer.getResourceString();
Set<String> errorMessages = null;
if (MetamodelContentType.XML.equals(deserializer.getContentType())) {
errorMessages = xmlValidator.validateSchema(file);
}
tobiaskraft marked this conversation as resolved.
Show resolved Hide resolved
else if (MetamodelContentType.JSON.equals(deserializer.getContentType())) {
errorMessages = jsonValidator.validateSchema(file);
}
return errorMessages;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2024 the original author or authors.
*
* 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;

/**
* Supported ContentType's for serializing and deserializing Metamodels.
*/
public enum MetamodelContentType {

JSON,
XML

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXDeserializer;
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.aasx.MetamodelContentType;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.AASSimple;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException;
import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException;
Expand Down Expand Up @@ -65,17 +66,26 @@ public void roundTrip() throws SerializationException, IOException, InvalidForma
fileList.add(inMemoryFile);
fileList.add(inMemoryFileThumbnail);

java.io.File file = tempFolder.newFile("output.aasx");

// check round trip with XML content
java.io.File file = tempFolder.newFile("output-xml.aasx");
new AASXSerializer().write(AASSimple.createEnvironment(), fileList, new FileOutputStream(file));

InputStream in = new FileInputStream(file);
AASXDeserializer deserializer = new AASXDeserializer(in);

assertEquals(AASSimple.createEnvironment(), deserializer.read());
assertTrue(CollectionUtils.isEqualCollection(fileList, deserializer.getRelatedFiles()));
}

// check round trip with JSON content
file = tempFolder.newFile("output-json.aasx");
new AASXSerializer().write(AASSimple.createEnvironment(), fileList, new FileOutputStream(file), MetamodelContentType.JSON);

in = new FileInputStream(file);
deserializer = new AASXDeserializer(in);

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();
Expand Down
Loading