content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  package com.cloud.agent.api.storage;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.stream.StreamResult;
  
  import org.apache.cloudstack.utils.security.ParserUtils;
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang.math.NumberUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.w3c.dom.traversal.DocumentTraversal;
  import org.w3c.dom.traversal.NodeFilter;
  import org.w3c.dom.traversal.NodeIterator;
  import org.xml.sax.SAXException;
  
  import com.cloud.agent.api.to.DatadiskTO;
  import com.cloud.agent.api.to.deployasis.OVFConfigurationTO;
  import com.cloud.agent.api.to.deployasis.OVFEulaSectionTO;
  import com.cloud.agent.api.to.deployasis.OVFNetworkTO;
  import com.cloud.agent.api.to.deployasis.OVFPropertyTO;
  import com.cloud.agent.api.to.deployasis.OVFVirtualHardwareItemTO;
  import com.cloud.agent.api.to.deployasis.OVFVirtualHardwareSectionTO;
  import com.cloud.configuration.Resource.ResourceType;
  import com.cloud.exception.InternalErrorException;
  import com.cloud.utils.Pair;
  import com.cloud.utils.compression.CompressionUtil;
  import com.cloud.utils.exception.CloudRuntimeException;
  
  public class OVFHelper {
      protected Logger logger = LogManager.getLogger(getClass());
  
      private final OVFParser ovfParser;
  
      public OVFHelper() {
          ovfParser = new OVFParser();
      }
  
      public OVFParser getOvfParser() {
          return this.ovfParser;
      }
  
      /**
       * Get disk virtual size given its values on fields: 'ovf:capacity' and 'ovf:capacityAllocationUnits'
       * @param capacity capacity
       * @param allocationUnits capacity allocation units
       * @return disk virtual size
       */
      public static Long getDiskVirtualSize(Long capacity, String allocationUnits, String ovfFilePath) throws InternalErrorException {
          if ((capacity != 0) && (allocationUnits != null)) {
              long units = 1;
              if (allocationUnits.equalsIgnoreCase("KB") || allocationUnits.equalsIgnoreCase("KiloBytes") || allocationUnits.equalsIgnoreCase("byte * 2^10")) {
                  units = ResourceType.bytesToKiB;
              } else if (allocationUnits.equalsIgnoreCase("MB") || allocationUnits.equalsIgnoreCase("MegaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^20")) {
                  units = ResourceType.bytesToMiB;
              } else if (allocationUnits.equalsIgnoreCase("GB") || allocationUnits.equalsIgnoreCase("GigaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^30")) {
                  units = ResourceType.bytesToGiB;
              }
              return capacity * units;
          } else {
              throw new InternalErrorException("Failed to read capacity and capacityAllocationUnits from the OVF file: " + ovfFilePath);
          }
      }
  
      /**
       * Create OVFProperty class from the parsed node. Note that some fields may not be present.
       * The key attribute is required
       */
      protected OVFPropertyTO createOVFPropertyFromNode(Node node, int index, String category) {
          Element element = (Element) node;
          String key = ovfParser.getNodeAttribute(element, "key");
          if (StringUtils.isBlank(key)) {
              return null;
          }
  
          String value = ovfParser.getNodeAttribute(element, "value");
          String type = ovfParser.getNodeAttribute(element, "type");
          String qualifiers = ovfParser.getNodeAttribute(element, "qualifiers");
          String userConfigurableStr = ovfParser.getNodeAttribute(element, "userConfigurable");
          boolean userConfigurable = StringUtils.isNotBlank(userConfigurableStr) &&
                  userConfigurableStr.equalsIgnoreCase("true");
          String passStr = ovfParser.getNodeAttribute(element, "password");
          boolean password = StringUtils.isNotBlank(passStr) && passStr.equalsIgnoreCase("true");
          String label = ovfParser.getChildNodeValue(node, "Label");
          String description = ovfParser.getChildNodeValue(node, "Description");
          logger.debug("Creating OVF property index " + index + (category == null ? "" : " for category " + category)
                  + " with key = " + key);
          return new OVFPropertyTO(key, type, value, qualifiers, userConfigurable,
                  label, description, password, index, category);
      }
  
      /**
       * Retrieve OVF properties from a parsed OVF file including its category (if available) and in-order,
       * with attribute 'ovf:userConfigurable' set to true.
       */
      public List<OVFPropertyTO> getConfigurableOVFPropertiesFromDocument(Document doc) {
          List<OVFPropertyTO> props = new ArrayList<>();
          if (doc == null) {
              return props;
          }
          int propertyIndex = 0;
          NodeList productSections = ovfParser.getElementsFromOVFDocument(doc, "ProductSection");
          if (productSections != null) {
              String lastCategoryFound = null;
              for (int i = 0; i < productSections.getLength(); i++) {
                  Node node = productSections.item(i);
                  if (node == null) {
                      continue;
                  }
                  NodeList childNodes = node.getChildNodes();
                  for (int j = 0; j < childNodes.getLength(); j++) {
                      Node child = childNodes.item(j);
                      if (child == null) {
                          continue;
                      }
                      if (child.getNodeName().equalsIgnoreCase("Category") ||
                              child.getNodeName().endsWith(":Category")) {
                          lastCategoryFound = child.getTextContent();
                          logger.info("Category found " + lastCategoryFound);
                      } else if (child.getNodeName().equalsIgnoreCase("Property") ||
                              child.getNodeName().endsWith(":Property")) {
                          OVFPropertyTO prop = createOVFPropertyFromNode(child, propertyIndex, lastCategoryFound);
                          if (prop != null && prop.isUserConfigurable()) {
                              props.add(prop);
                              propertyIndex++;
                          }
                      }
                  }
              }
          }
          return props;
      }
  
      /**
       * Get properties from OVF XML string
       */
      protected List<OVFPropertyTO> getOVFPropertiesFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getConfigurableOVFPropertiesFromDocument(doc);
      }
  
      protected Pair<String, String> getOperatingSystemInfoFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getOperatingSystemInfoFromDocument(doc);
      }
  
      protected List<OVFConfigurationTO> getOVFDeploymentOptionsFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getDeploymentOptionsFromDocumentTree(doc);
      }
  
      protected List<OVFVirtualHardwareItemTO> getOVFVirtualHardwareSectionFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getVirtualHardwareItemsFromDocumentTree(doc);
      }
  
      protected OVFVirtualHardwareSectionTO getVirtualHardwareSectionFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getVirtualHardwareSectionFromDocument(doc);
      }
  
      protected List<OVFEulaSectionTO> getOVFEulaSectionFromXmlString(final String ovfString) throws IOException, SAXException {
          final Document doc = ovfParser.parseOVF(ovfString);
          return getEulaSectionsFromDocument(doc);
      }
  
      public List<DatadiskTO> getOVFVolumeInfoFromFile(final String ovfFilePath, final String configurationId) throws InternalErrorException {
          if (StringUtils.isBlank(ovfFilePath)) {
              return new ArrayList<>();
          }
          Document doc = ovfParser.parseOVFFile(ovfFilePath);
  
          return getOVFVolumeInfoFromFile(ovfFilePath, doc, configurationId);
      }
  
      public List<DatadiskTO> getOVFVolumeInfoFromFile(String ovfFilePath, Document doc, String configurationId) throws InternalErrorException {
          if (StringUtils.isBlank(ovfFilePath)) {
              return null;
          }
  
          File ovfFile = new File(ovfFilePath);
          List<OVFVirtualHardwareItemTO> hardwareItems = getVirtualHardwareItemsFromDocumentTree(doc);
          List<OVFFile> files = extractFilesFromOvfDocumentTree(ovfFile, doc);
          List<OVFDisk> disks = extractDisksFromOvfDocumentTree(doc);
  
          List<OVFVirtualHardwareItemTO> diskHardwareItems = hardwareItems.stream()
                  .filter(x -> x.getResourceType() == OVFVirtualHardwareItemTO.HardwareResourceType.DiskDrive &&
                          hardwareItemContainsConfiguration(x, configurationId))
                  .collect(Collectors.toList());
          return matchHardwareItemsToDiskAndFilesInformation(diskHardwareItems, files, disks, ovfFile.getParent());
      }
  
      private String extractDiskIdFromDiskHostResource(String hostResource) {
          if (hostResource.startsWith("ovf:/disk/")) {
              return hostResource.replace("ovf:/disk/", "");
          }
          String[] resourceParts = hostResource.split("/");
          return resourceParts[resourceParts.length - 1];
      }
  
      private OVFDisk getDiskDefinitionFromDiskId(String diskId, List<OVFDisk> disks) {
          for (OVFDisk disk : disks) {
              if (disk._diskId.equalsIgnoreCase(diskId)) {
                  return disk;
              }
          }
          return null;
      }
  
      private List<DatadiskTO> matchHardwareItemsToDiskAndFilesInformation(List<OVFVirtualHardwareItemTO> diskHardwareItems,
                                                                           List<OVFFile> files, List<OVFDisk> disks,
                                                                           String ovfParentPath) throws InternalErrorException {
          List<DatadiskTO> diskTOs = new LinkedList<>();
          int diskNumber = 0;
          for (OVFVirtualHardwareItemTO diskItem : diskHardwareItems) {
              if (StringUtils.isBlank(diskItem.getHostResource())) {
                  logger.error("Missing disk information for hardware item " + diskItem.getElementName() + " " + diskItem.getInstanceId());
                  continue;
              }
              String diskId = extractDiskIdFromDiskHostResource(diskItem.getHostResource());
              OVFDisk diskDefinition = getDiskDefinitionFromDiskId(diskId, disks);
              if (diskDefinition == null) {
                  logger.error("Missing disk definition for disk ID " + diskId);
              }
              OVFFile fileDefinition = getFileDefinitionFromDiskDefinition(diskDefinition._fileRef, files);
              DatadiskTO datadiskTO = generateDiskTO(fileDefinition, diskDefinition, ovfParentPath, diskNumber, diskItem);
              diskTOs.add(datadiskTO);
              diskNumber++;
          }
          List<OVFFile> isoFiles = files.stream().filter(x -> x.isIso).collect(Collectors.toList());
          for (OVFFile isoFile : isoFiles) {
              DatadiskTO isoTO = generateDiskTO(isoFile, null, ovfParentPath, diskNumber, null);
              diskTOs.add(isoTO);
              diskNumber++;
          }
          return diskTOs;
      }
  
      private DatadiskTO generateDiskTO(OVFFile file, OVFDisk disk, String ovfParentPath, int diskNumber,
                                        OVFVirtualHardwareItemTO diskItem) throws InternalErrorException {
          String path = file != null ? ovfParentPath + File.separator + file._href : null;
          if (StringUtils.isNotBlank(path)) {
              File f = new File(path);
              if (!f.exists() || f.isDirectory()) {
                  logger.error("One of the attached disk or iso does not exists " + path);
                  throw new InternalErrorException("One of the attached disk or iso as stated on OVF does not exists " + path);
              }
          }
          Long capacity = disk != null ? disk._capacity : file._size;
          Long fileSize = file != null ? file._size : 0L;
  
          String controller = "";
          String controllerSubType = "";
          if (disk != null) {
              OVFDiskController cDiskController = disk._controller;
              controller = cDiskController == null ? "" : disk._controller._name;
              controllerSubType = cDiskController == null ? "" : disk._controller._subType;
          }
  
          boolean isIso = file != null && file.isIso;
          boolean bootable = file != null && file._bootable;
          String diskId = disk == null ? file._id : disk._diskId;
          String configuration = diskItem != null ? diskItem.getConfigurationIds() : null;
          return new DatadiskTO(path, capacity, fileSize, diskId,
                  isIso, bootable, controller, controllerSubType, diskNumber, configuration);
      }
  
      protected List<OVFDisk> extractDisksFromOvfDocumentTree(Document doc) {
          NodeList disks = ovfParser.getElementsFromOVFDocument(doc, "Disk");
          NodeList items = ovfParser.getElementsFromOVFDocument(doc, "Item");
  
          ArrayList<OVFDisk> vd = new ArrayList<>();
          for (int i = 0; i < disks.getLength(); i++) {
              Element disk = (Element) disks.item(i);
  
              if (disk == null) {
                  continue;
              }
              OVFDisk od = new OVFDisk();
              String virtualSize = ovfParser.getNodeAttribute(disk, "capacity");
              od._capacity = NumberUtils.toLong(virtualSize, 0L);
              String allocationUnits = ovfParser.getNodeAttribute(disk, "capacityAllocationUnits");
              od._diskId = ovfParser.getNodeAttribute(disk, "diskId");
              od._fileRef = ovfParser.getNodeAttribute(disk, "fileRef");
              od._populatedSize = NumberUtils.toLong(ovfParser.getNodeAttribute(disk, "populatedSize"));
  
              if ((od._capacity != 0) && (allocationUnits != null)) {
                  long units = 1;
                  if (allocationUnits.equalsIgnoreCase("KB") || allocationUnits.equalsIgnoreCase("KiloBytes") || allocationUnits.equalsIgnoreCase("byte * 2^10")) {
                      units = ResourceType.bytesToKiB;
                  } else if (allocationUnits.equalsIgnoreCase("MB") || allocationUnits.equalsIgnoreCase("MegaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^20")) {
                      units = ResourceType.bytesToMiB;
                  } else if (allocationUnits.equalsIgnoreCase("GB") || allocationUnits.equalsIgnoreCase("GigaBytes") || allocationUnits.equalsIgnoreCase("byte * 2^30")) {
                      units = ResourceType.bytesToGiB;
                  }
                  od._capacity = od._capacity * units;
              }
              od._controller = getControllerType(items, od._diskId);
              vd.add(od);
          }
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("found %d disk definitions",vd.size()));
          }
          return vd;
      }
  
      protected List<OVFFile> extractFilesFromOvfDocumentTree(File ovfFile, Document doc) {
          NodeList files = ovfParser.getElementsFromOVFDocument(doc, "File");
          ArrayList<OVFFile> vf = new ArrayList<>();
          boolean toggle = true;
          for (int j = 0; j < files.getLength(); j++) {
              Element file = (Element)files.item(j);
              OVFFile of = new OVFFile();
              of._href = ovfParser.getNodeAttribute(file, "href");
              if (of._href.endsWith("vmdk") || of._href.endsWith("iso")) {
                  of._id = ovfParser.getNodeAttribute(file, "id");
                  String size = ovfParser.getNodeAttribute(file, "size");
                  if (StringUtils.isNotBlank(size)) {
                      of._size = Long.parseLong(size);
                  } else {
                      String dataDiskPath = ovfFile.getParent() + File.separator + of._href;
                      File this_file = new File(dataDiskPath);
                      of._size = this_file.length();
                  }
                  of.isIso = of._href.endsWith("iso");
                  if (toggle && !of.isIso) {
                      of._bootable = true;
                      toggle = !toggle;
                  }
                  vf.add(of);
              }
          }
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("found %d file definitions in %s",vf.size(), ovfFile.getPath()));
          }
          return vf;
      }
  
      private OVFDiskController getControllerType(final NodeList itemList, final String diskId) {
          for (int k = 0; k < itemList.getLength(); k++) {
              Element item = (Element)itemList.item(k);
              NodeList cn = item.getChildNodes();
              for (int l = 0; l < cn.getLength(); l++) {
                  if (cn.item(l) instanceof Element) {
                      Element el = (Element)cn.item(l);
                      if ("rasd:HostResource".equals(el.getNodeName())
                              && (el.getTextContent().contains("ovf:/file/" + diskId) || el.getTextContent().contains("ovf:/disk/" + diskId))) {
                          Element oe = getParentNode(itemList, item);
                          Element voe = oe;
                          while (oe != null) {
                              voe = oe;
                              oe = getParentNode(itemList, voe);
                          }
                          return getController(voe);
                      }
                  }
              }
          }
          return null;
      }
  
      private Element getParentNode(final NodeList itemList, final Element childItem) {
          NodeList cn = childItem.getChildNodes();
          String parent_id = null;
          for (int l = 0; l < cn.getLength(); l++) {
              if (cn.item(l) instanceof Element) {
                  Element el = (Element)cn.item(l);
                  if ("rasd:Parent".equals(el.getNodeName())) {
                      parent_id = el.getTextContent();
                  }
              }
          }
          if (parent_id != null) {
              for (int k = 0; k < itemList.getLength(); k++) {
                  Element item = (Element)itemList.item(k);
                  NodeList child = item.getChildNodes();
                  for (int l = 0; l < child.getLength(); l++) {
                      if (child.item(l) instanceof Element) {
                          Element el = (Element)child.item(l);
                          if ("rasd:InstanceID".equals(el.getNodeName()) && el.getTextContent().trim().equals(parent_id)) {
                              return item;
                          }
                      }
                  }
              }
          }
          return null;
      }
  
      private OVFDiskController getController(Element controllerItem) {
          OVFDiskController dc = new OVFDiskController();
          NodeList child = controllerItem.getChildNodes();
          for (int l = 0; l < child.getLength(); l++) {
              if (child.item(l) instanceof Element) {
                  Element el = (Element)child.item(l);
                  if ("rasd:ElementName".equals(el.getNodeName())) {
                      dc._name = el.getTextContent();
                  }
                  if ("rasd:ResourceSubType".equals(el.getNodeName())) {
                      dc._subType = el.getTextContent();
                  }
              }
          }
          return dc;
      }
  
      public void rewriteOVFFileForSingleDisk(final String origOvfFilePath, final String newOvfFilePath, final String diskName) {
          final Document doc = ovfParser.parseOVFFile(origOvfFilePath);
  
          NodeList disks = ovfParser.getElementsFromOVFDocument(doc, "Disk");
          NodeList files = ovfParser.getElementsFromOVFDocument(doc, "File");
          NodeList items = ovfParser.getElementsFromOVFDocument(doc, "Item");
          String keepfile = null;
          List<Element> toremove = new ArrayList<>();
          for (int j = 0; j < files.getLength(); j++) {
              Element file = (Element)files.item(j);
              String href = ovfParser.getNodeAttribute(file, "href");
              if (diskName.equals(href)) {
                  keepfile = ovfParser.getNodeAttribute(file, "id");
              } else {
                  toremove.add(file);
              }
          }
          String keepdisk = null;
          for (int i = 0; i < disks.getLength(); i++) {
              Element disk = (Element)disks.item(i);
              String fileRef = ovfParser.getNodeAttribute(disk, "fileRef");
              if (keepfile == null) {
                  logger.info("FATAL: OVA format error");
              } else if (keepfile.equals(fileRef)) {
                  keepdisk = ovfParser.getNodeAttribute(disk, "diskId");
              } else {
                  toremove.add(disk);
              }
          }
          for (int k = 0; k < items.getLength(); k++) {
              Element item = (Element) items.item(k);
              NodeList cn = item.getChildNodes();
              for (int l = 0; l < cn.getLength(); l++) {
                  if (cn.item(l) instanceof Element) {
                      Element el = (Element) cn.item(l);
                      if ("rasd:HostResource".equals(el.getNodeName())
                              && !(el.getTextContent().contains("ovf:/file/" + keepdisk) || el.getTextContent().contains("ovf:/disk/" + keepdisk))) {
                          toremove.add(item);
                          break;
                      }
                  }
              }
          }
  
          for (Element rme : toremove) {
              if (rme.getParentNode() != null) {
                  rme.getParentNode().removeChild(rme);
              }
          }
  
          writeDocumentToFile(newOvfFilePath, doc);
      }
  
      private void writeDocumentToFile(String newOvfFilePath, Document doc) {
          try {
  
              final StringWriter writer = new StringWriter();
              final StreamResult result = new StreamResult(writer);
              final TransformerFactory tf = ParserUtils.getSaferTransformerFactory();
              final Transformer transformer = tf.newTransformer();
              final DOMSource domSource = new DOMSource(doc);
              transformer.transform(domSource, result);
              PrintWriter outfile = new PrintWriter(newOvfFilePath);
              outfile.write(writer.toString());
              outfile.close();
          } catch (IOException | TransformerException e) {
              logger.info("Unexpected exception caught while rewriting OVF:" + e.getMessage(), e);
              throw new CloudRuntimeException(e);
          }
      }
  
      OVFFile getFileDefinitionFromDiskDefinition(String fileRef, List<OVFFile> files) {
          for (OVFFile file : files) {
              if (file._id.equals(fileRef)) {
                  return file;
              }
          }
          return null;
      }
  
      public List<OVFNetworkTO> getNetPrerequisitesFromDocument(Document doc) throws InternalErrorException {
          if (doc == null) {
              if (logger.isTraceEnabled()) {
                  logger.trace("no document to parse; returning no prerequisite networks");
              }
              return Collections.emptyList();
          }
  
          Map<String, OVFNetworkTO> nets = getNetworksFromDocumentTree(doc);
  
          checkForOnlyOneSystemNode(doc);
  
          matchNicsToNets(nets, doc);
  
          return new ArrayList<>(nets.values());
      }
  
      private void matchNicsToNets(Map<String, OVFNetworkTO> nets, Node systemElement) {
          final DocumentTraversal traversal = (DocumentTraversal) systemElement;
          final NodeIterator iterator = traversal.createNodeIterator(systemElement, NodeFilter.SHOW_ELEMENT, null, true);
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("starting out with %d network-prerequisites, parsing hardware",nets.size()));
          }
          int nicCount = 0;
          for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
              final Element e = (Element) n;
              if ("rasd:Connection".equals(e.getTagName())) {
                  nicCount++;
                  String name = e.getTextContent(); // should be in our nets
                  if(nets.get(name) == null) {
                      if(logger.isInfoEnabled()) {
                          logger.info(String.format("found a nic definition without a network definition byname %s, adding it to the list.", name));
                      }
                      nets.put(name, new OVFNetworkTO());
                  }
                  OVFNetworkTO thisNet = nets.get(name);
                  if (e.getParentNode() != null) {
                      fillNicPrerequisites(thisNet,e.getParentNode());
                  }
              }
          }
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("ending up with %d network-prerequisites, parsed %d nics", nets.size(), nicCount));
          }
      }
  
      /**
       * get all the stuff from parent node
       *
       * @param nic the object to carry through the system
       * @param parentNode the xml container node for nic data
       */
      private void fillNicPrerequisites(OVFNetworkTO nic, Node parentNode) {
          String addressOnParentStr = ovfParser.getChildNodeValue(parentNode, "AddressOnParent");
          String automaticAllocationStr = ovfParser.getChildNodeValue(parentNode, "AutomaticAllocation");
          String description = ovfParser.getChildNodeValue(parentNode, "Description");
          String elementName = ovfParser.getChildNodeValue(parentNode, "ElementName");
          String instanceIdStr = ovfParser.getChildNodeValue(parentNode, "InstanceID");
          String resourceSubType = ovfParser.getChildNodeValue(parentNode, "ResourceSubType");
          String resourceType = ovfParser.getChildNodeValue(parentNode, "ResourceType");
  
          try {
              int addressOnParent = Integer.parseInt(addressOnParentStr);
              nic.setAddressOnParent(addressOnParent);
          } catch (NumberFormatException e) {
              logger.warn("Encountered element of type \"AddressOnParent\", that could not be parse to an integer number: " + addressOnParentStr);
          }
  
          boolean automaticAllocation = StringUtils.isNotBlank(automaticAllocationStr) && Boolean.parseBoolean(automaticAllocationStr);
          nic.setAutomaticAllocation(automaticAllocation);
          nic.setNicDescription(description);
          nic.setElementName(elementName);
  
          try {
              int instanceId = Integer.parseInt(instanceIdStr);
              nic.setInstanceID(instanceId);
          } catch (NumberFormatException e) {
              logger.warn("Encountered element of type \"InstanceID\", that could not be parse to an integer number: " + instanceIdStr);
          }
  
          nic.setResourceSubType(resourceSubType);
          nic.setResourceType(resourceType);
      }
  
      private void checkForOnlyOneSystemNode(Document doc) throws InternalErrorException {
          // get hardware VirtualSystem, for now we support only one of those
          NodeList systemElements = ovfParser.getElementsFromOVFDocument(doc, "VirtualSystem");
          if (systemElements.getLength() != 1) {
              String msg = "found " + systemElements.getLength() + " system definitions in OVA, can only handle exactly one.";
              logger.warn(msg);
              throw new InternalErrorException(msg);
          }
      }
  
      private Map<String, OVFNetworkTO> getNetworksFromDocumentTree(Document doc) {
          NodeList networkElements = ovfParser.getElementsFromOVFDocument(doc,"Network");
          Map<String, OVFNetworkTO> nets = new HashMap<>();
          for (int i = 0; i < networkElements.getLength(); i++) {
  
              Element networkElement = (Element)networkElements.item(i);
              String networkName = ovfParser.getNodeAttribute(networkElement, "name");
  
              String description = ovfParser.getChildNodeValue(networkElement, "Description");
  
              OVFNetworkTO network = new OVFNetworkTO();
              network.setName(networkName);
              network.setNetworkDescription(description);
  
              nets.put(networkName,network);
          }
          if (logger.isTraceEnabled()) {
              logger.trace(String.format("found %d networks in template", nets.size()));
          }
          return nets;
      }
  
      private boolean hardwareItemContainsConfiguration(OVFVirtualHardwareItemTO item, String configurationId) {
          if (StringUtils.isAnyBlank(configurationId, item.getConfigurationIds())) {
              return true;
          }
          String configurationIds = item.getConfigurationIds();
          if (StringUtils.isNotBlank(configurationIds)) {
              String[] configurations = configurationIds.split(" ");
              List<String> confList = Arrays.asList(configurations);
              return confList.contains(configurationId);
          }
          return false;
      }
  
      /**
       * Retrieve the virtual hardware section and its deployment options as configurations
       */
      public OVFVirtualHardwareSectionTO getVirtualHardwareSectionFromDocument(Document doc) {
          List<OVFConfigurationTO> configurations = getDeploymentOptionsFromDocumentTree(doc);
          List<OVFVirtualHardwareItemTO> items = getVirtualHardwareItemsFromDocumentTree(doc);
          if (CollectionUtils.isNotEmpty(configurations)) {
              for (OVFConfigurationTO configuration : configurations) {
                  List<OVFVirtualHardwareItemTO> confItems = items.stream().
                          filter(x -> StringUtils.isNotBlank(x.getConfigurationIds())
                                  && hardwareItemContainsConfiguration(x, configuration.getId()))
                          .collect(Collectors.toList());
                  configuration.setHardwareItems(confItems);
              }
          }
          List<OVFVirtualHardwareItemTO> commonItems = null;
          if (CollectionUtils.isNotEmpty(items)) {
              commonItems = items.stream().filter(x -> StringUtils.isBlank(x.getConfigurationIds())).collect(Collectors.toList());
          }
          String minimumHardwareVersion = getMinimumHardwareVersionFromDocumentTree(doc);
          return new OVFVirtualHardwareSectionTO(configurations, commonItems, minimumHardwareVersion);
      }
  
      private String getMinimumHardwareVersionFromDocumentTree(Document doc) {
          String version = null;
          if (doc != null) {
              NodeList systemNodeList = ovfParser.getElementsFromOVFDocument(doc, "System");
              if (systemNodeList.getLength() != 0) {
                  Node systemItem = systemNodeList.item(0);
                  String hardwareVersions = ovfParser.getChildNodeValue(systemItem, "VirtualSystemType");
                  if (StringUtils.isNotBlank(hardwareVersions)) {
                      String[] versions = hardwareVersions.split(",");
                      // Order the hardware versions and retrieve the minimum version
                      List<String> versionsList = Arrays.stream(versions).sorted().collect(Collectors.toList());
                      version = versionsList.get(0);
                  }
              }
          }
          return version;
      }
  
      private List<OVFConfigurationTO> getDeploymentOptionsFromDocumentTree(Document doc) {
          List<OVFConfigurationTO> options = new ArrayList<>();
          if (doc == null) {
              return options;
          }
          NodeList deploymentOptionSection = ovfParser.getElementsFromOVFDocument(doc,"DeploymentOptionSection");
          if (deploymentOptionSection.getLength() == 0) {
              return options;
          }
          Node hardwareSectionNode = deploymentOptionSection.item(0);
          NodeList childNodes = hardwareSectionNode.getChildNodes();
          int index = 0;
          for (int i = 0; i < childNodes.getLength(); i++) {
              Node node = childNodes.item(i);
              if (node != null && (node.getNodeName().equals("Configuration") || node.getNodeName().equals("ovf:Configuration"))) {
                  Element configuration = (Element) node;
                  String configurationId = ovfParser.getNodeAttribute(configuration, "id");
                  String description = ovfParser.getChildNodeValue(configuration, "Description");
                  String label = ovfParser.getChildNodeValue(configuration, "Label");
                  OVFConfigurationTO option = new OVFConfigurationTO(configurationId, label, description, index);
                  options.add(option);
                  index++;
              }
          }
          return options;
      }
  
      private List<OVFVirtualHardwareItemTO> getVirtualHardwareItemsFromDocumentTree(Document doc) {
          List<OVFVirtualHardwareItemTO> items = new LinkedList<>();
          if (doc == null) {
              return items;
          }
          NodeList hardwareSection = ovfParser.getElementsFromOVFDocument(doc, "VirtualHardwareSection");
          if (hardwareSection.getLength() == 0) {
              return items;
          }
          Node hardwareSectionNode = hardwareSection.item(0);
          NodeList childNodes = hardwareSectionNode.getChildNodes();
          for (int i = 0; i < childNodes.getLength(); i++) {
              Node node = childNodes.item(i);
              if (node != null && (node.getNodeName().equals("Item") || node.getNodeName().equals("ovf:Item"))) {
                  Element configuration = (Element) node;
                  String configurationIds = ovfParser.getNodeAttribute(configuration, "configuration");
                  String allocationUnits = ovfParser.getChildNodeValue(configuration, "AllocationUnits");
                  String description = ovfParser.getChildNodeValue(configuration, "Description");
                  String elementName = ovfParser.getChildNodeValue(configuration, "ElementName");
                  String instanceID = ovfParser.getChildNodeValue(configuration, "InstanceID");
                  String limit = ovfParser.getChildNodeValue(configuration, "Limit");
                  String reservation = ovfParser.getChildNodeValue(configuration, "Reservation");
                  String resourceType = ovfParser.getChildNodeValue(configuration, "ResourceType");
                  String virtualQuantity = ovfParser.getChildNodeValue(configuration, "VirtualQuantity");
                  String hostResource = ovfParser.getChildNodeValue(configuration, "HostResource");
                  String addressOnParent = ovfParser.getChildNodeValue(configuration, "AddressOnParent");
                  String parent = ovfParser.getChildNodeValue(configuration, "Parent");
                  OVFVirtualHardwareItemTO item = new OVFVirtualHardwareItemTO();
                  item.setConfigurationIds(configurationIds);
                  item.setAllocationUnits(allocationUnits);
                  item.setDescription(description);
                  item.setElementName(elementName);
                  item.setInstanceId(instanceID);
                  item.setLimit(getLongValueFromString(limit));
                  item.setReservation(getLongValueFromString(reservation));
                  Integer resType = getIntValueFromString(resourceType);
                  if (resType != null) {
                      item.setResourceType(OVFVirtualHardwareItemTO.getResourceTypeFromId(resType));
                  }
                  item.setVirtualQuantity(getLongValueFromString(virtualQuantity));
                  item.setHostResource(hostResource);
                  item.setAddressOnParent(addressOnParent);
                  item.setParent(parent);
                  items.add(item);
              }
          }
          return items;
      }
  
      private Long getLongValueFromString(String value) {
          if (StringUtils.isNotBlank(value)) {
              try {
                  return Long.parseLong(value);
              } catch (NumberFormatException e) {
                  logger.debug("Could not parse the value: " + value + ", ignoring it");
              }
          }
          return null;
      }
  
      private Integer getIntValueFromString(String value) {
          if (StringUtils.isNotBlank(value)) {
              try {
                  return Integer.parseInt(value);
              } catch (NumberFormatException e) {
                  logger.debug("Could not parse the value: " + value + ", ignoring it");
              }
          }
          return null;
      }
  
      protected byte[] compressOVFEula(String license) throws IOException {
          CompressionUtil compressionUtil = new CompressionUtil();
          return compressionUtil.compressString(license);
      }
  
      public List<OVFEulaSectionTO> getEulaSectionsFromDocument(Document doc) {
          List<OVFEulaSectionTO> eulas = new LinkedList<>();
          if (doc == null) {
              return eulas;
          }
          NodeList eulaSections = ovfParser.getElementsFromOVFDocument(doc, "EulaSection");
          int eulaIndex = 0;
          if (eulaSections.getLength() > 0) {
              for (int index = 0; index < eulaSections.getLength(); index++) {
                  Node eulaNode = eulaSections.item(index);
                  NodeList eulaChildNodes = eulaNode.getChildNodes();
                  String eulaInfo = null;
                  String eulaLicense = null;
                  for (int i = 0; i < eulaChildNodes.getLength(); i++) {
                      Node eulaItem = eulaChildNodes.item(i);
                      if (eulaItem.getNodeName().equalsIgnoreCase("Info")
                              || eulaItem.getNodeName().endsWith(":Info")) {
                          eulaInfo = eulaItem.getTextContent();
                      } else if (eulaItem.getNodeName().equalsIgnoreCase("License")
                              || eulaItem.getNodeName().endsWith(":License")) {
                          eulaLicense = eulaItem.getTextContent();
                      }
                  }
                  byte[] compressedLicense = new byte[0];
                  try {
                      compressedLicense = compressOVFEula(eulaLicense);
                  } catch (IOException e) {
                      logger.error("Could not compress the license for info " + eulaInfo);
                      continue;
                  }
                  OVFEulaSectionTO eula = new OVFEulaSectionTO(eulaInfo, compressedLicense, eulaIndex);
                  eulas.add(eula);
                  eulaIndex++;
              }
          }
  
          return eulas;
      }
  
      public Pair<String, String> getOperatingSystemInfoFromDocument(Document doc) {
          if (doc == null) {
              return null;
          }
          NodeList guesOsList = ovfParser.getElementsFromOVFDocument(doc, "OperatingSystemSection");
          if (guesOsList.getLength() == 0) {
              return null;
          }
          Node guestOsNode = guesOsList.item(0);
          Element guestOsElement = (Element) guestOsNode;
          String osType = ovfParser.getNodeAttribute(guestOsElement, "osType");
          String description = ovfParser.getChildNodeValue(guestOsNode, "Description");
          return new Pair<>(osType, description);
      }
  
      class OVFFile {
          // <File ovf:href="i-2-8-VM-disk2.vmdk" ovf:id="file1" ovf:size="69120" />
          public String _href;
          public String _id;
          public Long _size;
          public boolean _bootable;
          public boolean isIso;
      }
  
      class OVFDisk {
          //<Disk ovf:capacity="50" ovf:capacityAllocationUnits="byte * 2^20" ovf:diskId="vmdisk2" ovf:fileRef="file2"
          //ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" ovf:populatedSize="43319296" />
          public Long _capacity;
          public String _diskId;
          public String _fileRef;
          public Long _populatedSize;
          public OVFDiskController _controller;
      }
  
      class OVFDiskController {
          public String _name;
          public String _subType;
      }
  }
  