protected List<String> getJobParents(
      NodeTemplate nodeTemplate, String nodeName, Map<String, NodeTemplate> nodes) {
    // Get Chronos parent job dependency
    String parentJobCapabilityName = "parent_job";
    Map<String, NodeTemplate> parentJobs =
        toscaService.getAssociatedNodesByCapability(nodes, nodeTemplate, parentJobCapabilityName);

    if (parentJobs.isEmpty()) {
      return null;
    } else {
      // WARNING: cycle check is done later!
      return Lists.newArrayList(parentJobs.keySet());
    }
  }
  protected Job createJob(
      Map<String, NodeTemplate> nodes,
      String deploymentId,
      String nodeName,
      NodeTemplate nodeTemplate,
      Map<String, Resource> resources) {
    try {
      Job chronosJob = new Job();
      // Init job infos

      // Get the generated UUID for the node (in DB resource ?)
      // FIXME This is just for prototyping... Otherwise is madness!!
      Resource resourceJob = resources.get(nodeName);

      chronosJob.setName(resourceJob.getId());

      // TODO Validation
      chronosJob.setRetries(
          Integer.parseInt(
              (String)
                  toscaService.getNodePropertyValueByName(nodeTemplate, "retries").getValue()));

      chronosJob.setCommand(
          (String) toscaService.getNodePropertyValueByName(nodeTemplate, "command").getValue());

      // TODO Enable epsilon setting in TOSCA tplt ?
      chronosJob.setEpsilon("PT10S");

      ListPropertyValue inputUris =
          (ListPropertyValue) toscaService.getNodePropertyValueByName(nodeTemplate, "uris");
      if (inputUris != null && !inputUris.getValue().isEmpty()) {
        // Convert List<Object> to List<String>
        chronosJob.setUris(
            inputUris
                .getValue()
                .stream()
                .map(e -> ((PropertyValue<?>) e).getValue().toString())
                .collect(Collectors.toList()));
      }

      List<EnvironmentVariable> envs = new ArrayList<>();
      ComplexPropertyValue inputEnvVars =
          ((ComplexPropertyValue)
              toscaService.getNodePropertyValueByName(nodeTemplate, "environment_variables"));
      if (inputEnvVars != null) {
        for (Map.Entry<String, Object> var : inputEnvVars.getValue().entrySet()) {
          EnvironmentVariable envVar = new EnvironmentVariable();
          envVar.setName(var.getKey());
          envVar.setValue(((PropertyValue<?>) var.getValue()).getValue().toString());
          envs.add(envVar);
        }
        chronosJob.setEnvironmentVariables(envs);
      }

      // Docker image
      // TODO Remove hard-coded?
      String supportedType = "tosca.artifacts.Deployment.Image.Container.Docker";
      DeploymentArtifact image;
      // <image> artifact available
      if (nodeTemplate.getArtifacts() == null
          || (image = nodeTemplate.getArtifacts().get("image")) == null) {
        throw new IllegalArgumentException(
            String.format(
                "<image> artifact not found in node <%s> of type <%s>",
                nodeName, nodeTemplate.getType()));
      }

      // <image> artifact type check
      if (!image.getArtifactType().equals(supportedType)) {
        throw new IllegalArgumentException(
            String.format(
                "Unsupported artifact type for <image> artifact in node <%s> of type <%s>. "
                    + "Given <%s>, supported <%s>",
                nodeName,
                nodeTemplate.getType(),
                nodeTemplate.getArtifacts().get("image").getArtifactType(),
                supportedType));
      }

      // Requirements

      // Get Docker host dependency
      String dockerCapabilityName = "host";
      Map<String, NodeTemplate> dockerRelationships =
          toscaService.getAssociatedNodesByCapability(nodes, nodeTemplate, dockerCapabilityName);
      Double dockerNumCpus = null;
      Double dockerMemSize = null;
      if (!dockerRelationships.isEmpty()) {
        /*
         * WARNING: The TOSCA validation should already check the limits (currently Alien4Cloud does
         * not...)
         */
        NodeTemplate dockerNode = dockerRelationships.values().iterator().next();
        Capability dockerCapability = dockerNode.getCapabilities().get(dockerCapabilityName);
        dockerNumCpus =
            Double.parseDouble(
                (String)
                    toscaService
                        .getCapabilityPropertyValueByName(dockerCapability, "num_cpus")
                        .getValue());

        // Converting Memory Size (as TOSCA scalar-unit.size)
        SizeType tmp = new SizeType();
        String memSizeRaw =
            (String)
                toscaService
                    .getCapabilityPropertyValueByName(dockerCapability, "mem_size")
                    .getValue();
        dockerMemSize = tmp.parse(memSizeRaw).convert("MB"); // Chronos wants MB
      }

      Container container = new Container();
      container.setType("DOCKER");

      // Run the container in Privileged Mode
      Parameters param = new Parameters();
      param.setKey("privileged");
      param.setValue("true");
      Collection<Parameters> parameters = new ArrayList<Parameters>();
      parameters.add(param);
      container.setParameters(parameters);

      // FIXME ForcePullImage must be parametrizable by tosca template
      container.setForcePullImage(true);
      ////////////////////////////////////////////////////////////////

      container.setImage(
          (String)
              ((PropertyValue<?>) nodeTemplate.getArtifacts().get("image").getFile()).getValue());
      if (dockerNumCpus != null) {
        chronosJob.setCpus(dockerNumCpus);
      }
      if (dockerMemSize != null) {
        chronosJob.setMem(dockerMemSize);
      }

      chronosJob.setContainer(container);

      return chronosJob;
    } catch (Exception ex) {
      throw new RuntimeException(
          String.format(
              "Failed to parse node <%s> of type <%s>: %s",
              nodeName, nodeTemplate.getType(), ex.getMessage()),
          ex);
    }
  }
  /**
   * Creates the {@link INDIGOJob} graph based on the given {@link Deployment} (the TOSCA template
   * is parsed).
   *
   * @param deployment the input deployment.
   * @return the job graph.
   */
  protected Map<String, IndigoJob> generateJobGraph(
      Deployment deployment, Map<String, OneData> odParameters) {
    String deploymentId = deployment.getId();
    Map<String, IndigoJob> jobs = new HashMap<String, ChronosServiceImpl.IndigoJob>();

    // Parse TOSCA template
    Map<String, NodeTemplate> nodes = null;
    try {
      String customizedTemplate = deployment.getTemplate();
      /*
       * FIXME TEMPORARY - Replace hard-coded properties in nodes (WARNING: Cannot be done when
       * receiving the template because we still miss OneData settings that are obtained during the
       * WF after the site choice, which in turns depends on the template nodes and properties...)
       */
      customizedTemplate = replaceHardCodedParams(customizedTemplate, odParameters);

      // Re-parse template (TODO: serialize the template in-memory representation?)
      ArchiveRoot ar = toscaService.prepareTemplate(customizedTemplate, deployment.getParameters());

      nodes = ar.getTopology().getNodeTemplates();
    } catch (IOException | ParsingException ex) {
      throw new OrchestratorException(ex);
    }

    // TODO Iterate on Chronos nodes and related dependencies (just ignore others - also if invalid
    // - for now)

    // Populate resources (nodes) hashmap to speed up job creation (id-name mapping is needed)
    Map<String, Resource> resources =
        deployment
            .getResources()
            .stream()
            .collect(Collectors.toMap(e -> e.getToscaNodeName(), e -> e));

    // Only create Indigo Jobs
    for (Map.Entry<String, NodeTemplate> node : nodes.entrySet()) {
      NodeTemplate nodeTemplate = node.getValue();
      String nodeName = node.getKey();
      if (isChronosNode(nodeTemplate)) {
        Job chronosJob = createJob(nodes, deploymentId, nodeName, nodeTemplate, resources);
        IndigoJob job = new IndigoJob(nodeName, chronosJob);
        jobs.put(nodeName, job);
      }
    }

    // Create jobs hierarchy
    for (Map.Entry<String, IndigoJob> job : jobs.entrySet()) {
      IndigoJob indigoJob = job.getValue();
      String nodeName = job.getKey();
      NodeTemplate nodeTemplate = nodes.get(nodeName);

      // Retrieve Job parents
      List<String> parentNames = getJobParents(nodeTemplate, nodeName, nodes);

      if (parentNames != null && !parentNames.isEmpty()) {
        List<String> chronosParentList = new ArrayList<>();

        for (String parentName : parentNames) {
          IndigoJob parentJob = jobs.get(parentName);
          // Add this job to the parent
          parentJob.getChildren().add(indigoJob);
          // Add the parent to this job
          indigoJob.getParents().add(parentJob);

          // Add to the Chronos DSL parent list
          chronosParentList.add(parentJob.getChronosJob().getName());
        }

        // Update Chronos DSL parent list
        indigoJob.getChronosJob().setParents(chronosParentList);
      }
    }

    // Validate (no cycles!)
    // FIXME Shouldn't just return the topological order ?
    getJobsTopologicalOrder(jobs);

    return jobs;
  }