blob: f2f534e88cb3935e99172478fed43261d1a3fe9c [file] [log] [blame]
package org.eclipse.basyx.components.aas.aasx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.IOUtils;
import org.eclipse.basyx.components.configuration.BaSyxConfiguration;
import org.eclipse.basyx.components.xml.XMLAASBundleFactory;
import org.eclipse.basyx.support.bundle.AASBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* The AASX package converter converts a aasx package into a list of aas, a list
* of submodels a list of assets, a list of Concept descriptions
*
* The aas provides the references to the submodels and assets
*
* @author zhangzai
*
*/
public class AASXPackageManager {
/**
* Path to the AASX package
*/
private String aasxPath;
/**
* AAS bundle factory
*/
private XMLAASBundleFactory bundleFactory;
/**
* Logger
*/
private static Logger logger = LoggerFactory.getLogger(AASXPackageManager.class);
/**
* Constructor
*/
public AASXPackageManager(String path) {
aasxPath = path;
}
public Set<AASBundle> retrieveAASBundles() throws IOException, ParserConfigurationException, SAXException {
bundleFactory = new XMLAASBundleFactory(getXMLResourceString(aasxPath));
return bundleFactory.create();
}
/**
* Find the path of the aas-xml file
*
* @param stream - Stream of the aasx package
* @return Path of the aas xml file, empty string if not found
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
private String findAASXml(ZipInputStream stream) throws IOException, ParserConfigurationException, SAXException {
String path = "";
// find the entry of the aasx
for (ZipEntry entry; (entry = stream.getNextEntry()) != null;) {
// get name of the entry
String name = entry.getName();
// find the relationship file in the directory /aasx/_rels/aas_origin.rels
if (!entry.isDirectory() && name.startsWith("aasx/_rels")) {
// find the file aasx-origin.rels
if (name.endsWith("aasx-origin.rels")) {
// Get path of the aas xml
String aasXmlPath = findAASXMLAddress(stream);
if (!aasXmlPath.isEmpty()) {
path = aasXmlPath;
break;
}
}
}
}
return path;
}
/**
* Get entry of a file
*
* @param filename - name of a file with path
* @return a file entry
* @throws IOException
*/
private ZipInputStream returnFileEntryStream(String filename, ZipInputStream stream) throws IOException {
ZipInputStream str = null;
if (filename.startsWith("/")) {
filename = filename.substring(1);
}
// get all entries of the aasx
for (ZipEntry e; (e = stream.getNextEntry()) != null;) {
// get name of the entry
String name = e.getName();
if (name.equals(filename)) {
str = stream;
break;
}
}
return str;
}
/**
* Parse the relationship file and find the path of the aas-XML file describing
* the aas
*
* @param ins - input stream of this relationship file
* @return path of the aas-xml file
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
private String findAASXMLAddress(InputStream ins) throws ParserConfigurationException, SAXException, IOException {
String path = "";
// create the XML document parser
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(ins);
doc.getDocumentElement().normalize();
// Get the tag with "Relationships"
logger.info("Root element :" + doc.getDocumentElement().getNodeName());
NodeList relList = doc.getElementsByTagName("Relationship");
// If there is only 1 relationship pointing to the aas-xml file, this should be
// the case
if (relList.getLength() == 1) {
Node first = relList.item(0);
if (first.getNodeType() == Node.ELEMENT_NODE) {
logger.info("\nCurrent Element :" + first.getNodeName());
// get the target file path
String targetFile = ((Element) first).getAttribute("Target");
String type = ((Element) first).getAttribute("Type");
// validate the relationship type
if (type.endsWith("aas-spec")) {
logger.info("target file name : " + targetFile);
path = targetFile;
}
}
}
return path;
}
/**
* Return the Content of the xml file in the aasx-package as String
*
* @param filePath - path to the aasx package
* @return Content of XML as String
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
private String getXMLResourceString(String filePath) throws IOException, ParserConfigurationException, SAXException {
String aasXmlPath;
// Create the zip input stream
try (ZipInputStream stream = getZipInputStream(filePath)) {
// find the path of the aas xml
aasXmlPath = this.findAASXml(stream);
}
try (ZipInputStream stream = getZipInputStream(filePath)) {
// Find the entry of the aas xml
ZipInputStream streamPointingToEntry = this.returnFileEntryStream(aasXmlPath, stream);
// create the xml-converter with the input stream
String text = IOUtils.toString(streamPointingToEntry, StandardCharsets.UTF_8.name());
return text;
}
}
/**
* Load the referenced filepaths in the submodels such as PDF, PNG files from
* the package
*
* @return a map of the folder name and folder path, the folder holds the files
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*
*/
private List<String> parseReferencedFilePathsFromAASX(String aasxFilePath)
throws IOException, ParserConfigurationException, SAXException {
String xmlPath;
logger.info("AASX filepath: " + aasxFilePath);
try (ZipInputStream stream = getZipInputStream(aasxFilePath)) {
// find the aasx xml file
xmlPath = this.findAASXml(stream);
}
try (ZipInputStream stream = getZipInputStream(aasxFilePath)) {
// find the relationship file next to the aas xml file
String[] xmlPathParts = xmlPath.split("/");
String relPath = xmlPath.substring(0, xmlPath.lastIndexOf("/")) + "/_rels/" + xmlPathParts[xmlPathParts.length - 1] + ".rels";
// Find the entry of the xml .rel file
ZipInputStream streamPointingToEntry = this.returnFileEntryStream(relPath, stream);
// Return all files referenced in this relationship file
return parseReferencedFilePathsFromRelationship(streamPointingToEntry);
}
}
/**
* Unzips all files referenced by the aasx file according to its relationships
*
* @param filePath - path the AASX
*
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* @throws URISyntaxException
*/
public void unzipRelatedFiles(String aasxFilePath)
throws IOException, ParserConfigurationException, SAXException, URISyntaxException {
// load folder which stores the files
List<String> files = parseReferencedFilePathsFromAASX(aasxFilePath);
for (String filePath : files) {
// name of the folder
unzipFile(filePath, aasxPath);
}
}
/**
* Create a folder to hold the unpackaged files The folder has the path
* \target\classes\docs
*
* @throws IOException
* @throws URISyntaxException
*/
private Path getRootFolder() throws IOException, URISyntaxException {
URI uri = AASXPackageManager.class.getProtectionDomain().getCodeSource().getLocation().toURI();
URI parent = new File(uri).getParentFile().toURI();
return Paths.get(parent);
}
/**
* unzip the file folders
*
* @param filePath - path of the file in the aasx to unzip
* @param aasxPath - aasx path
* @throws IOException
* @throws URISyntaxException
*/
private void unzipFile(String filePath, String aasxPath)
throws IOException, URISyntaxException {
// Create destination directory
if (filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
logger.info("Unzipping " + filePath + " to root folder:");
String relativePath = filePath.substring(0, filePath.lastIndexOf("/"));
Path rootPath = getRootFolder();
Path destDir = rootPath.resolve(relativePath);
logger.info("Unzipping to " + destDir);
Files.createDirectories(destDir);
// create buffer for the folder binary
byte[] buffer = new byte[1024];
// Find the file with the "filePath"
try (ZipInputStream stream = getZipInputStream(aasxPath)) {
ZipEntry zipEntry = stream.getNextEntry();
while (zipEntry != null) {
if (!zipEntry.isDirectory() && zipEntry.getName().contains(filePath)) {
// Create the file object in the destination directory
File newFile = newFile(destDir.toFile(), zipEntry);
// Create the file output stream
try (FileOutputStream fos = new FileOutputStream(newFile)) {
int len;
// Write the binary to the file
while ((len = stream.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
return;
}
zipEntry = stream.getNextEntry();
}
}
}
/**
* Preventing Zip Slip, create a file
*
* @param destinationDir
* @param zipEntry
* @return
* @throws IOException
*/
private File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
int i = zipEntry.getName().lastIndexOf("/");
String filename = zipEntry.getName().substring(i);
File destFile = new File(destinationDir, filename);
String destDirPath = destinationDir.getCanonicalPath();
String destFilePath = destFile.getCanonicalPath();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
}
return destFile;
}
/**
* Find path of the referenced file with reference type aas-suppl
*
* @param insRelFile - the input stream of the relationship file
* @return
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
private List<String> parseReferencedFilePathsFromRelationship(InputStream insRelFile)
throws ParserConfigurationException, SAXException, IOException {
List<String> files = new ArrayList<>();
if (insRelFile == null) {
return files;
}
// create the XML document parser
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(insRelFile);
doc.getDocumentElement().normalize();
// Get the tag with "Relationships"
NodeList relList = doc.getElementsByTagName("Relationship");
for (int i = 0; i < relList.getLength(); i++) {
Node node = relList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
// get the target file path
String targetFile = ((Element) node).getAttribute("Target");
String type = ((Element) node).getAttribute("Type");
// validate the relationship type
if (type.endsWith("aas-suppl")) {
files.add(targetFile);
}
}
}
return files;
}
private ZipInputStream getZipInputStream(String aasxFilePath) throws IOException {
try {
return new ZipInputStream(BaSyxConfiguration.getResourceStream(aasxFilePath));
} catch (NullPointerException ex) {
// Alternativ, if resource has not been found: load from a file
return new ZipInputStream(new FileInputStream(aasxFilePath));
}
}
}