blob: 37b0980f3fbe1b73b9a084cfe9556d085f327124 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.components.aas.aasx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
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.Collection;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.eclipse.basyx.components.configuration.BaSyxConfiguration;
import org.eclipse.basyx.components.xml.XMLAASBundleFactory;
import org.eclipse.basyx.submodel.metamodel.api.ISubModel;
import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement;
import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElementCollection;
import org.eclipse.basyx.submodel.metamodel.api.submodelelement.dataelement.IFile;
import org.eclipse.basyx.support.bundle.AASBundle;
import org.eclipse.basyx.vab.modelprovider.VABPathTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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, conradi
*
*/
public class AASXPackageManager {
private static final String XML_TYPE = "http://www.admin-shell.io/aasx/relationships/aas-spec";
private static final String AASX_ORIGIN = "/aasx/aasx-origin";
/**
* Path to the AASX package
*/
private String aasxPath;
/**
* AAS bundle factory
*/
private XMLAASBundleFactory bundleFactory;
/**
* Cache for generated Bundles
*/
private Set<AASBundle> bundles;
/**
* Logger
*/
private static Logger logger = LoggerFactory.getLogger(AASXPackageManager.class);
/**
* Constructor
*/
public AASXPackageManager(String path) {
aasxPath = path;
}
public Set<AASBundle> retrieveAASBundles() throws IOException, ParserConfigurationException, SAXException, InvalidFormatException {
// If the XML was already parsed return cached Bundles
if(bundles != null) {
return bundles;
}
OPCPackage aasxRoot = OPCPackage.open(getInputStream(aasxPath));
bundleFactory = new XMLAASBundleFactory(getXMLResourceString(aasxRoot));
bundles = bundleFactory.create();
return bundles;
}
/**
* Return the Content of the xml file in the aasx-package as String
*
* @param aasxPackage - the root package of the AASX
* @return Content of XML as String
* @throws InvalidFormatException
* @throws IOException
*/
private String getXMLResourceString(OPCPackage aasxPackage) throws InvalidFormatException, IOException {
// Get the "/aasx/aasx-origin" Part. It is Relationship source for the XML-Document
PackagePart originPart = aasxPackage.getPart(PackagingURIHelper.createPartName(AASX_ORIGIN));
// Get the Relation to the XML Document
PackageRelationshipCollection originRelationships = originPart.getRelationshipsByType(XML_TYPE);
// If there is more than one or no XML-Document that is an error
if(originRelationships.size() > 1) {
throw new RuntimeException("More than one 'aasx-spec' document found in .aasx");
} else if(originRelationships.size() == 0) {
throw new RuntimeException("No 'aasx-spec' document found in .aasx");
}
// Get the PackagePart of the XML-Document
PackagePart xmlPart = originPart.getRelatedPart(originRelationships.getRelationship(0));
// Read the content from the PackagePart
InputStream stream = xmlPart.getInputStream();
StringWriter writer = new StringWriter();
IOUtils.copy(stream, writer, StandardCharsets.UTF_8);
return writer.toString();
}
/**
* 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
* @throws InvalidFormatException
*
*/
private List<String> parseReferencedFilePathsFromAASX()
throws IOException, ParserConfigurationException, SAXException, InvalidFormatException {
Set<AASBundle> bundles = retrieveAASBundles();
List<ISubModel> submodels = new ArrayList<>();
// Get the Submodels from all AASBundles
for(AASBundle bundle: bundles) {
submodels.addAll(bundle.getSubmodels());
}
List<String> paths = new ArrayList<String>();
for(ISubModel sm: submodels) {
paths.addAll(parseElements(sm.getSubmodelElements().values()));
}
return paths;
}
/**
* Gets the paths from a collection of ISubmodelElement
*
* @param elements
* @return the Paths from the File elements
*/
private List<String> parseElements(Collection<ISubmodelElement> elements) {
List<String> paths = new ArrayList<String>();
for(ISubmodelElement element: elements) {
if(element instanceof IFile) {
IFile file = (IFile) element;
// If the path contains a "://", we can assume, that the Path is a link to an other server
// e.g. http://localhost:8080/aasx/...
if(!file.getValue().contains("://")) {
paths.add(file.getValue());
}
}
else if(element instanceof ISubmodelElementCollection) {
ISubmodelElementCollection collection = (ISubmodelElementCollection) element;
paths.addAll(parseElements(collection.getSubmodelElements().values()));
}
}
return paths;
}
/**
* Unzips all files referenced by the aasx file according to its relationships
*
*
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* @throws URISyntaxException
* @throws InvalidFormatException
*/
public void unzipRelatedFiles()
throws IOException, ParserConfigurationException, SAXException, URISyntaxException, InvalidFormatException {
// load folder which stores the files
List<String> files = parseReferencedFilePathsFromAASX();
OPCPackage aasxRoot = OPCPackage.open(getInputStream(aasxPath));
for (String filePath : files) {
// name of the folder
unzipFile(filePath, aasxRoot);
}
}
/**
* 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
* @throws InvalidFormatException
*/
private void unzipFile(String filePath, OPCPackage aasxRoot) throws IOException, URISyntaxException, InvalidFormatException {
// Create destination directory
if (filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
logger.info("Unzipping " + filePath + " to root folder:");
String relativePath = "files/" + VABPathTools.getParentPath(filePath);
Path rootPath = getRootFolder();
Path destDir = rootPath.resolve(relativePath);
logger.info("Unzipping to " + destDir);
Files.createDirectories(destDir);
PackagePart part = aasxRoot.getPart(PackagingURIHelper.createPartName("/" + filePath));
if(part == null) {
logger.error("File '" + filePath + "' cloud not be unzipped. It does not exist in .aasx.");
throw new FileNotFoundException("File '" + filePath + "' cloud not be unzipped. It does not exist in .aasx.");
}
String targetPath = destDir.toString() + "/" + VABPathTools.getLastElement(filePath);
InputStream stream = part.getInputStream();
FileUtils.copyInputStreamToFile(stream, new File(targetPath));
}
private InputStream getInputStream(String aasxFilePath) throws IOException {
InputStream stream = BaSyxConfiguration.getResourceStream(aasxFilePath);
if(stream != null) {
return stream;
} else {
// Alternativ, if resource has not been found: load from a file
try {
return new FileInputStream(aasxFilePath);
} catch (FileNotFoundException e) {
logger.error("File '" + aasxFilePath + "' to be loaded was not found.");
throw e;
}
}
}
}