| /******************************************************************************* |
| * 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.aas.factory.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.aas.bundle.AASBundle; |
| import org.eclipse.basyx.aas.bundle.AASBundleFactory; |
| import org.eclipse.basyx.aas.factory.xml.XMLToMetamodelConverter; |
| 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.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 AASXToMetamodelConverter { |
| |
| private static final String XML_TYPE = "http://www.admin-shell.io/aasx/relationships/aas-spec"; |
| private static final String AASX_ORIGIN = "/aasx/aasx-origin"; |
| |
| private String aasxPath; |
| private OPCPackage aasxRoot; |
| private InputStream aasxInputStream; |
| |
| private Set<AASBundle> bundles; |
| |
| private static Logger logger = LoggerFactory.getLogger(AASXToMetamodelConverter.class); |
| |
| public AASXToMetamodelConverter(String path) { |
| this.aasxPath = path; |
| } |
| |
| public AASXToMetamodelConverter(InputStream stream) { |
| aasxInputStream = stream; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T extends AASBundle> Set<T> retrieveAASBundles() throws IOException, ParserConfigurationException, SAXException, InvalidFormatException { |
| |
| // If the XML was already parsed return cached Bundles |
| if (bundles != null) { |
| return (Set<T>) bundles; |
| } |
| |
| loadAASX(); |
| |
| String xmlContent = getXMLResourceString(aasxRoot); |
| XMLToMetamodelConverter converter = new XMLToMetamodelConverter(xmlContent); |
| |
| bundles = new AASBundleFactory().create(converter.parseAAS(), converter.parseSubmodels(), converter.parseAssets()); |
| |
| closeOPCPackage(); |
| |
| return (Set<T>) bundles; |
| } |
| |
| private void loadAASX() throws IOException, InvalidFormatException { |
| if (aasxInputStream == null) { |
| aasxInputStream = getInputStream(aasxPath); |
| } |
| |
| if (aasxRoot == null) { |
| aasxRoot = OPCPackage.open(aasxInputStream); |
| } |
| } |
| |
| private void closeOPCPackage() throws IOException { |
| aasxRoot.close(); |
| } |
| |
| /** |
| * 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<? extends 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 |
| loadAASX(); |
| |
| List<String> files = parseReferencedFilePathsFromAASX(); |
| for (String filePath : files) { |
| // name of the folder |
| unzipFile(filePath, aasxRoot); |
| } |
| |
| closeOPCPackage(); |
| } |
| |
| /** |
| * Create a folder to hold the unpackaged files The folder has the path |
| * \target\classes\docs |
| * |
| * @throws IOException |
| * @throws URISyntaxException |
| */ |
| protected Path getRootFolder() throws IOException, URISyntaxException { |
| URI uri = AASXToMetamodelConverter.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); |
| } |
| if (filePath.isEmpty()) { |
| logger.warn("A file with empty path can not be unzipped."); |
| return; |
| } |
| 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.warn("File '" + filePath + "' could not be unzipped. It does not exist in .aasx."); |
| return; |
| } |
| |
| 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 = 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; |
| } |
| } |
| } |
| |
| private static InputStream getResourceStream(String relativeResourcePath) { |
| ClassLoader classLoader = AASXToMetamodelConverter.class.getClassLoader(); |
| return classLoader.getResourceAsStream(relativeResourcePath); |
| } |
| } |