Refactors AASXPackageManager to be able to load files outside /aasx

Refactors AASServer to store files in /files folder; not in server root

Signed-off-by: Maximilian Conradi <maximilian.conradi@iese.fraunhofer.de>
Change-Id: I1296489722c76b34a4f5abb9f9e1d76971ce94bc
diff --git a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java
index 4fe537b..a642b8b 100644
--- a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java
+++ b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java
@@ -137,7 +137,7 @@
 		// An initial AAS has been loaded from the drive?
 		if (aasBundles != null) {
 			// 1. Also provide the files
-			context.addServletMapping("/aasx/*", new DefaultServlet());
+			context.addServletMapping("/files/*", new DefaultServlet());
 
 			// 2. Fix the file paths according to the servlet configuration
 			modifyFilePaths(contextConfig.getHostname(), contextConfig.getPort(), contextConfig.getContextPath());
@@ -185,7 +185,7 @@
 		AASXPackageManager packageManager = new AASXPackageManager(aasxPath);
 
 		// Unpack the files referenced by the aas
-		packageManager.unzipRelatedFiles(aasxPath);
+		packageManager.unzipRelatedFiles();
 
 		// Retrieve the aas from the package
 		this.aasBundles = packageManager.retrieveAASBundles();
@@ -271,6 +271,7 @@
 	 * Fixes the File submodel element value paths according to the given endpoint configuration
 	 */
 	private void modifyFilePaths(String hostName, int port, String rootPath) {
+		rootPath = rootPath + "/files";
 		for (AASBundle bundle : aasBundles) {
 			Set<ISubModel> submodels = bundle.getSubmodels();
 			for (ISubModel sm : submodels) {
diff --git a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/aasx/AASXPackageManager.java b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/aasx/AASXPackageManager.java
index f2f534e..8a116ab 100644
--- a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/aasx/AASXPackageManager.java
+++ b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/aasx/AASXPackageManager.java
@@ -12,6 +12,7 @@
 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 java.util.zip.ZipEntry;
@@ -24,7 +25,12 @@
 import org.apache.commons.io.IOUtils;
 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.w3c.dom.Document;
@@ -53,6 +59,11 @@
 	 * AAS bundle factory
 	 */
 	private XMLAASBundleFactory bundleFactory;
+	
+	/**
+	 * Cache for generated Bundles
+	 */
+	private Set<AASBundle> bundles;
 
 	/**
 	 * Logger
@@ -67,9 +78,17 @@
 	}
 
 	public Set<AASBundle> retrieveAASBundles() throws IOException, ParserConfigurationException, SAXException {
+		
+		// If the XML was already parsed return cached Bundles
+		if(bundles != null) {
+			return bundles;
+		}
+		
 		bundleFactory = new XMLAASBundleFactory(getXMLResourceString(aasxPath));
 		
-		return bundleFactory.create();
+		bundles = bundleFactory.create();
+		
+		return bundles;
 	}
 
 	/**
@@ -212,42 +231,65 @@
 	 * @throws ParserConfigurationException
 	 * 
 	 */
-	private List<String> parseReferencedFilePathsFromAASX(String aasxFilePath)
+	private List<String> parseReferencedFilePathsFromAASX()
 			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);
+		
+		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>();
 
-		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);
+		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
 	 * 
-	 * @param filePath - path the AASX
 	 * 
 	 * @throws IOException
 	 * @throws SAXException
 	 * @throws ParserConfigurationException
 	 * @throws URISyntaxException
 	 */
-	public void unzipRelatedFiles(String aasxFilePath)
+	public void unzipRelatedFiles()
 			throws IOException, ParserConfigurationException, SAXException, URISyntaxException {
 		// load folder which stores the files
-		List<String> files = parseReferencedFilePathsFromAASX(aasxFilePath);
+		List<String> files = parseReferencedFilePathsFromAASX();
 		for (String filePath : files) {
 			// name of the folder
 			unzipFile(filePath, aasxPath);
@@ -282,7 +324,7 @@
 			filePath = filePath.substring(1);
 		}
 		logger.info("Unzipping " + filePath + " to root folder:");
-		String relativePath = filePath.substring(0, filePath.lastIndexOf("/"));
+		String relativePath = "files/" + VABPathTools.getParentPath(filePath);
 		Path rootPath = getRootFolder();
 		Path destDir = rootPath.resolve(relativePath);
 		logger.info("Unzipping to " + destDir);
@@ -323,8 +365,7 @@
 	 * @throws IOException
 	 */
 	private File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
-		int i = zipEntry.getName().lastIndexOf("/");
-		String filename = zipEntry.getName().substring(i);
+		String filename = VABPathTools.getLastElement(zipEntry.getName());
 
 		File destFile = new File(destinationDir, filename);
 
@@ -337,47 +378,6 @@
 
 		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 {
diff --git a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java
index 7aef875..d651f80 100644
--- a/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java
+++ b/components/basys.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java
@@ -92,8 +92,8 @@
 
 	@Test
 	public void testGetSingleModule() throws Exception {
-		final String FILE_ENDING = "aasx/Nameplate/marking_rcm.jpg";
-		final String FILE_PATH = rootEndpoint + "aasx/Nameplate/marking_rcm.jpg";
+		final String FILE_ENDING = "files/aasx/Nameplate/marking_rcm.jpg";
+		final String FILE_PATH = rootEndpoint + "files/aasx/Nameplate/marking_rcm.jpg";
 		checkFile(FILE_PATH);
 
 		// Get the submdoel nameplate
@@ -125,13 +125,6 @@
 		Map<String, ISubModel> submodels = aas.getSubModels();
 		logger.info("# Submodels: " + submodels.size());
 		for (ISubModel sm : submodels.values()) {
-			// FIXME: In Identification, there's a file referenced that is not contained in aasx folder. 
-			// Since the current code only works with files in /aasx folder, this will create an error for now
-			// Remove this after this issue is fixed!
-			if (sm.getIdShort().equals("Identification")) {
-				continue;
-			}
-			
 			logger.info("Checking submodel: " + sm.getIdShort());
 			checkElementCollectionFiles(sm.getSubmodelElements().values());
 		}