| 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; |
| } |
| } |
| } |
| } |