/*
 * 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 org.apache.ambari.server.stack.upgrade;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;

import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.stack.upgrade.Task.Type;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.spi.upgrade.UpgradeType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents an upgrade pack.
 */
@XmlRootElement(name="upgrade")
@XmlAccessorType(XmlAccessType.FIELD)
public class UpgradePack {

  private static final String ALL_VERSIONS = "*";

  private static final Logger LOG = LoggerFactory.getLogger(UpgradePack.class);

  /**
   * Name of the file without the extension, such as upgrade-2.2
   */
  private String name;

  @XmlElement(name="source")
  private String source;

  @XmlElement(name="source-stack")
  private String sourceStack;


  @XmlElement(name="target")
  private String target;

  @XmlElement(name="target-stack")
  private String targetStack;

  @XmlElementWrapper(name="order")
  @XmlElement(name="group")
  private List<Grouping> groups = new ArrayList<>();

  @XmlElement(name="prerequisite-checks")
  private PrerequisiteChecks prerequisiteChecks;

  @XmlElement(name="orchestration-options-class")
  private String orchestrationOptionsClass;

  /**
   * In the case of a rolling upgrade, will specify processing logic for a particular component.
   * NonRolling upgrades are simpler so the "processing" is embedded into the  group's "type", which is a function like
   * "stop" or "start".
   */
  @XmlElementWrapper(name="processing")
  @XmlElement(name="service")
  private List<ProcessingService> processing;

  /**
   * {@code true} to automatically skip slave/client component failures. The
   * default is {@code false}.
   */
  @XmlElement(name = "skip-failures")
  private boolean skipFailures = false;

  /**
   * {@code true} to allow downgrade, {@code false} to disable downgrade.
   * Tag is optional and can be {@code null}, use {@code isDowngradeAllowed} getter instead.
   */
  @XmlElement(name = "downgrade-allowed", required = false, defaultValue = "true")
  private boolean downgradeAllowed = true;

  /**
   * {@code true} to automatically skip service check failures. The default is
   * {@code false}.
   */
  @XmlElement(name = "skip-service-check-failures")
  private boolean skipServiceCheckFailures = false;

  /**
   * Initialized once by {@link #afterUnmarshal(Unmarshaller, Object)}.
   */
  @XmlTransient
  private Map<String, Map<String, ProcessingComponent>> m_process = null;

  /**
   * A mapping of SERVICE/COMPONENT to any {@link AddComponentTask} instances.
   */
  @XmlTransient
  private final Map<String, AddComponentTask> m_addComponentTasks = new LinkedHashMap<>();

  @XmlTransient
  private boolean m_resolvedGroups = false;

  @XmlElement(name="type", defaultValue="rolling")
  private UpgradeType type;

  @XmlElementWrapper(name="upgrade-path")
  @XmlElement(name="intermediate-stack")
  private List<IntermediateStack> intermediateStacks;

  @XmlTransient
  private StackId ownerStackId;

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    return Objects.hash(type, source, sourceStack, target, targetStack, ownerStackId);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }

    if (!(object instanceof UpgradePack)) {
      return false;
    }

    UpgradePack that = (UpgradePack) object;

    return new EqualsBuilder()
      .append(type, that.type)
      .append(source, that.source)
      .append(sourceStack, that.sourceStack)
      .append(target, that.targetStack)
      .append(ownerStackId, that.ownerStackId)
      .isEquals();
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  /**
   * @return the source version for the upgrade pack
   */
  public String getSource() {
    return source;
  }

  /**
   * @return the source stack
   */
  public String getSourceStack() {
    return sourceStack;
  }

  /**
   * @return the target version for the upgrade pack
   */
  public String getTarget() {
    return target;
  }

  /**
   * @return the target stack
   */
  public String getTargetStack() {
    return targetStack;
  }

  /**
   * @return the type of upgrade, e.g., "ROLLING" or "NON_ROLLING"
   */
  public UpgradeType getType() {
    return type;
  }

  /**
   * @return the preCheck name, e.g. "CheckDescription"
   */
  public List<String> getPrerequisiteChecks() {
    if (prerequisiteChecks == null) {
      return new ArrayList<>();
    }
    return new ArrayList<>(prerequisiteChecks.checks);
  }

  /**
   *
   * @return the prerequisite check configuration
   */
  public PrerequisiteCheckConfig getPrerequisiteCheckConfig() {
    if (prerequisiteChecks == null) {
      return new PrerequisiteCheckConfig();
    }
    return prerequisiteChecks.configuration;
  }

  /**
   * Merges the prerequisite checks section of the upgrade xml with
   * the prerequisite checks from a service's upgrade xml.
   * These are added to the end of the current list of checks.
   *
   * @param pack
   *              the service's upgrade pack
   */
  public void mergePrerequisiteChecks(UpgradePack pack) {
    PrerequisiteChecks newPrereqChecks = pack.prerequisiteChecks;
    if (prerequisiteChecks == null) {
      prerequisiteChecks = newPrereqChecks;
      return;
    }

    if (newPrereqChecks == null) {
      return;
    }

    if (prerequisiteChecks.checks == null) {
      prerequisiteChecks.checks = new ArrayList<>();
    }
    if (newPrereqChecks.checks != null) {
      prerequisiteChecks.checks.addAll(newPrereqChecks.checks);
    }

    if (newPrereqChecks.configuration == null) {
      return;
    }

    if (prerequisiteChecks.configuration == null) {
      prerequisiteChecks.configuration = newPrereqChecks.configuration;
      return;
    }
    if (prerequisiteChecks.configuration.globalProperties == null) {
      prerequisiteChecks.configuration.globalProperties = new ArrayList<>();
    }
    if (prerequisiteChecks.configuration.prerequisiteCheckProperties == null) {
      prerequisiteChecks.configuration.prerequisiteCheckProperties = new ArrayList<>();
    }
    if (newPrereqChecks.configuration.globalProperties != null) {
      prerequisiteChecks.configuration.globalProperties.addAll(newPrereqChecks.configuration.globalProperties);
    }
    if (newPrereqChecks.configuration.prerequisiteCheckProperties != null) {
      prerequisiteChecks.configuration.prerequisiteCheckProperties.addAll(newPrereqChecks.configuration.prerequisiteCheckProperties);
    }
  }

/**
 * Merges the processing section of the upgrade xml with
 * the processing section from a service's upgrade xml.
 * These are added to the end of the current list of services.
 *
 * @param pack
 *              the service's upgrade pack
 */
  public void mergeProcessing(UpgradePack pack) {
    List<ProcessingService> list = pack.processing;
    if (list == null) {
      return;
    }
    if (processing == null) {
      processing = list;
      return;
    }
    processing.addAll(list);

    // new processing has been created, so rebuild the mappings
    initializeProcessingComponentMappings();
  }

  /**
   * Gets a list of stacks which are between the current stack version and the
   * target stack version inclusive. For example, if upgrading from HDP-2.2 to
   * HDP-2.4, this should include HDP-2.3 and HDP-2.4.
   * <p/>
   * This method is used to combine the correct configuration packs for a
   * specific upgrade from
   * {@link AmbariMetaInfo#getConfigUpgradePack(String, String)}.
   *
   * @return a list of intermediate stacks (target stack inclusive) or
   *         {@code null} if none.
   */
  public List<IntermediateStack> getIntermediateStacks() {
    return intermediateStacks;
  }


  /**
   * Gets whether skippable components that failed are automatically skipped.
   *
   * @return the skipComponentFailures
   */
  public boolean isComponentFailureAutoSkipped() {
    return skipFailures;
  }

  /**
   * Gets whether skippable service checks that failed are automatically
   * skipped.
   *
   * @return the skipServiceCheckFailures
   */
  public boolean isServiceCheckFailureAutoSkipped() {
    return skipServiceCheckFailures;
  }

  public List<Grouping> getAllGroups() {
    return groups;
  }

  /**
   * Gets the groups defined for the upgrade pack. If a direction is defined for
   * a group, it must match the supplied direction to be returned
   *
   * @param direction
   *          the direction to return the ordered groups
   * @return the list of groups
   */
  public List<Grouping> getGroups(Direction direction) {
    List<Grouping> list;
    if (direction.isUpgrade()) {
      list = groups;
    } else {
      switch (type) {
        case NON_ROLLING:
          list = getDowngradeGroupsForNonrolling();
          break;
        case HOST_ORDERED:
        case ROLLING:
        default:
          list = getDowngradeGroupsForRolling();
          break;
      }
    }

    List<Grouping> checked = new ArrayList<>();
    for (Grouping group : list) {
      if (null == group.intendedDirection || direction == group.intendedDirection) {
        checked.add(group);
      }

    }

    return checked;
  }

  /**
   * @return {@code true} if upgrade pack supports downgrade or {@code false} if not.
   */
  public boolean isDowngradeAllowed(){
    return downgradeAllowed;
  }

  public boolean canBeApplied(String targetVersion){
    // check that upgrade pack can be applied to selected stack
    // converting 2.2.*.* -> 2\.2(\.\d+)?(\.\d+)?(-\d+)?
    String regexPattern = getTarget().replaceAll("\\.", "\\\\."); // . -> \.
    regexPattern = regexPattern.replaceAll("\\\\\\.\\*", "(\\\\\\.\\\\d+)?"); // \.* -> (\.\d+)?
    regexPattern = regexPattern.concat("(-\\d+)?");
    return Pattern.matches(regexPattern, targetVersion);
  }

  /**
   * Calculates the group orders when performing a rolling downgrade
   * <ul>
   *   <li>ClusterGroupings must remain at the same positions (first/last).</li>
   *   <li>When there is a ServiceCheck group, it must ALWAYS follow the same</li>
   *       preceding group, whether for an upgrade or a downgrade.</li>
   *   <li>All other groups must follow the reverse order.</li>
   * </ul>
   * For example, give the following order of groups:
   * <ol>
   *   <li>PRE_CLUSTER</li>
   *   <li>ZK</li>
   *   <li>CORE_MASTER</li>
   *   <li>SERVICE_CHECK_1</li>
   *   <li>CLIENTS</li>
   *   <li>FLUME</li>
   *   <li>SERVICE_CHECK_2</li>
   *   <li>POST_CLUSTER</li>
   * </ol>
   * The reverse would be:
   * <ol>
   *   <li>PRE_CLUSTER</li>
   *   <li>FLUME</li>
   *   <li>SERVICE_CHECK_2</li>
   *   <li>CLIENTS</li>
   *   <li>CORE_MASTER</li>
   *   <li>SERVICE_CHECK_1</li>
   *   <li>ZK</li>
   *   <li>POST_CLUSTER</li>
   * </ol>
   * @return the list of groups, reversed appropriately for a downgrade.
   */
  private List<Grouping> getDowngradeGroupsForRolling() {
    List<Grouping> reverse = new ArrayList<>();

    // !!! Testing exposed groups.size() == 1 issue.  Normally there's no precedent for
    // a one-group upgrade pack, so take it into account anyway.
    if (groups.size() == 1) {
      return groups;
    }

    int idx = 0;
    int iter = 0;
    Iterator<Grouping> it = groups.iterator();

    while (it.hasNext()) {
      Grouping g = it.next();
      if (ClusterGrouping.class.isInstance(g)) {
        reverse.add(g);
        idx++;
      } else {
        if (iter+1 < groups.size()) {
          Grouping peek = groups.get(iter+1);
          if (ServiceCheckGrouping.class.isInstance(peek)) {
            reverse.add(idx, it.next());
            reverse.add(idx, g);
            iter++;
          } else {
            reverse.add(idx, g);
          }
        }
      }
      iter++;
    }

    return reverse;
  }

  private List<Grouping> getDowngradeGroupsForNonrolling() {
    return new ArrayList<>(groups);
  }

  /**
   * Gets the tasks by which services and components should be upgraded.
   * @return a map of service_name -> map(component_name -> process).
   */
  public Map<String, Map<String, ProcessingComponent>> getTasks() {
    return m_process;
  }

  /**
   * Gets a mapping of SERVICE/COMPONENT to {@link AddComponentTask} for this
   * upgrade pack.
   *
   * @return
   */
  public Map<String, AddComponentTask> getAddComponentTasks() {
    return m_addComponentTasks;
  }

  /**
   * This method is called after all the properties (except IDREF) are
   * unmarshalled for this object, but before this object is set to the parent
   * object. This is done automatically by the {@link Unmarshaller}.
   * <p/>
   * Currently, this method performs the following post-unmarshal operations:
   * <ul>
   * <li>Builds a mapping of service name to a mapping of component name to
   * {@link ProcessingComponent}</li>
   * </ul>
   *
   * @param unmarshaller
   *          the unmarshaller used to unmarshal this instance
   * @param parent
   *          the parent object, if any, that this will be set on.
   * @see Unmarshaller
   */
  void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
    initializeProcessingComponentMappings();
    initializeAddComponentTasks();
  }

  /**
   * Builds a mapping of component name to {@link ProcessingComponent} and then
   * maps those to service name, initializing {@link #m_process} to the result.
   */
  private void initializeProcessingComponentMappings() {
    m_process = new LinkedHashMap<>();

    if (CollectionUtils.isEmpty(processing)) {
      return;
    }

    for (ProcessingService svc : processing) {
      Map<String, ProcessingComponent> componentMap = m_process.get(svc.name);

      // initialize mapping if not present for the given service name
      if (null == componentMap) {
        componentMap = new LinkedHashMap<>();
        m_process.put(svc.name, componentMap);
      }

      for (ProcessingComponent pc : svc.components) {
        if (pc != null) {
          componentMap.put(pc.name, pc);
        } else {
          LOG.warn("ProcessingService {} has null amongst it's values (total {} components)",
              svc.name, svc.components.size());
        }
      }
    }
  }

  /**
   * Builds a mapping of SERVICE/COMPONENT to {@link AddComponentTask}.
   */
  private void initializeAddComponentTasks() {
    for (Grouping group : groups) {
      if (ClusterGrouping.class.isInstance(group)) {
        List<ExecuteStage> executeStages = ((ClusterGrouping) group).executionStages;
        for (ExecuteStage executeStage : executeStages) {
          Task task = executeStage.task;

          // keep track of this for later ...
          if (task.getType() == Type.ADD_COMPONENT) {
            AddComponentTask addComponentTask = (AddComponentTask) task;
            m_addComponentTasks.put(addComponentTask.getServiceAndComponentAsString(),
                addComponentTask);
          }
        }
      }
    }
  }

  /**
   * @return {@code true} if the upgrade targets any version or stack.  Both
   * {@link #target} and {@link #targetStack} must equal "*"
   */
  public boolean isAllTarget() {
    return ALL_VERSIONS.equals(target) && ALL_VERSIONS.equals(targetStack);
  }


  /**
   * A service definition that holds a list of components in the 'order' element.
   */
  public static class OrderService {

    @XmlAttribute(name="name")
    public String serviceName;

    @XmlElement(name="component")
    public List<String> components;
  }

  /**
   * A service definition in the 'processing' element.
   */
  public static class ProcessingService {

    @XmlAttribute
    public String name;

    @XmlElement(name="component")
    public List<ProcessingComponent> components;
  }

  /**
   * A component definition in the 'processing/service' path.
   */
  public static class ProcessingComponent {

    @XmlAttribute
    public String name;

    @XmlElementWrapper(name="pre-upgrade")
    @XmlElement(name="task")
    public List<Task> preTasks;

    @XmlElement(name="pre-downgrade")
    private DowngradeTasks preDowngradeXml;
    @XmlTransient
    public List<Task> preDowngradeTasks;

    @XmlElementWrapper(name="upgrade")
    @XmlElement(name="task")
    public List<Task> tasks;

    @XmlElementWrapper(name="post-upgrade")
    @XmlElement(name="task")
    public List<Task> postTasks;


    @XmlElement(name="post-downgrade")
    private DowngradeTasks postDowngradeXml;
    @XmlTransient
    public List<Task> postDowngradeTasks;

    /**
     * This method verifies that if {@code pre-upgrade} is defined, that there is also
     * a sibling {@code pre-downgrade} defined.  Similarly, {@code post-upgrade} must have a
     * sibling {@code post-downgrade}.  This is because the JDK (include 1.8) JAXB doesn't
     * support XSD xpath assertions for siblings.
     *
     * @param unmarshaller  the unmarshaller
     * @param parent        the parent
     */
    void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
      if (null != preDowngradeXml) {
        preDowngradeTasks = preDowngradeXml.copyUpgrade ? preTasks :
          preDowngradeXml.tasks;
      }

      if (null != postDowngradeXml) {
        postDowngradeTasks = postDowngradeXml.copyUpgrade ? postTasks :
          postDowngradeXml.tasks;
      }

      ProcessingService service = (ProcessingService) parent;

      if (LOG.isDebugEnabled()) {
        LOG.debug("Processing component {}/{} preUpgrade={} postUpgrade={} preDowngrade={} postDowngrade={}",
            preTasks, preDowngradeTasks, postTasks, postDowngradeTasks);
      }

      if (null != preTasks && null == preDowngradeTasks) {
        String error = String.format("Upgrade pack must contain pre-downgrade elements if "
            + "pre-upgrade exists for processing component %s/%s", service.name, name);

        throw new RuntimeException(error);
      }

      if (null != postTasks && null == postDowngradeTasks) {
        String error = String.format("Upgrade pack must contain post-downgrade elements if "
            + "post-upgrade exists for processing component %s/%s", service.name, name);

        throw new RuntimeException(error);
      }

      // !!! check for config tasks and mark the associated service
      initializeTasks(service.name, preTasks);
      initializeTasks(service.name, postTasks);
      initializeTasks(service.name, tasks);
      initializeTasks(service.name, preDowngradeTasks);
      initializeTasks(service.name, postDowngradeTasks);
    }

    /**
     * Checks for config tasks and marks the associated service.
     * @param service
     *          the service name
     * @param tasks
     *          the list of tasks to check
     */
    private void initializeTasks(String service, List<Task> tasks) {
      if (CollectionUtils.isEmpty(tasks)) {
        return;
      }

      for (Task task : tasks) {
        if (Task.Type.CONFIGURE == task.getType()) {
          ((ConfigureTask) task).associatedService = service;
        } else if (Task.Type.CREATE_AND_CONFIGURE == task.getType()) {
          ((CreateAndConfigureTask) task).associatedService = service;
        }
      }
    }
  }

  /**
   * An intermediate stack definition in
   * upgrade/upgrade-path/intermediate-stack path
   */
  public static class IntermediateStack {

    @XmlAttribute
    public String version;
  }

  /**
   * Container class to specify list of additional prerequisite checks to run in addition to the
   * required prerequisite checks and configuration properties for all prerequisite checks
   */
  public static class PrerequisiteChecks {
    /**
     * List of additional prerequisite checks to run in addition to required prerequisite checks
     */
    @XmlElement(name="check", type=String.class)
    public List<String> checks = new ArrayList<>();

    /**
     * Prerequisite checks configuration
     */
    @XmlElement(name="configuration")
    public PrerequisiteCheckConfig configuration;
  }

  /**
   * Prerequisite checks configuration
   */
  public static class PrerequisiteCheckConfig {
    /**
     * Global config properties common to all prereq checks
     */
    @XmlElement(name="property")
    public List<PrerequisiteProperty> globalProperties;

    /**
     * Config properties for individual prerequisite checks
     */
    @XmlElement(name="check-properties")
    public List<PrerequisiteCheckProperties> prerequisiteCheckProperties;

    /**
     * Get global config properties as a map
     * @return Map of global config properties
     */
    public Map<String, String> getGlobalProperties() {
      if(globalProperties == null) {
        return null;
      }
      Map<String, String> result = new HashMap<>();
      for (PrerequisiteProperty property : globalProperties) {
        result.put(property.name, property.value);
      }
      return result;
    }

    /**
     * Gets a mashup of all check properties, including global ones.
     *
     * @return a map of all check properties defined in the upgrade pack.
     */
    public Map<String, String> getAllProperties() {
      Map<String, String> properties = new HashMap<>();
      if( null != globalProperties ) {
        for (PrerequisiteProperty property : globalProperties) {
          properties.put(property.name, property.value);
        }
      }

      if (null != prerequisiteCheckProperties) {
        for(PrerequisiteCheckProperties checkProperties : prerequisiteCheckProperties) {
            properties.putAll(checkProperties.getProperties());
        }
      }

      return properties;
    }

    /**
     * Get config properties for a given prerequisite check as a map
     * @param checkName The prerequisite check name
     * @return Map of config properties for the prerequisite check
     */
    public Map<String, String> getCheckProperties(String checkName) {
      if(prerequisiteCheckProperties == null) {
        return null;
      }
      for(PrerequisiteCheckProperties checkProperties : prerequisiteCheckProperties) {
        if(checkProperties.name.equalsIgnoreCase(checkName)) {
          return checkProperties.getProperties();
        }
      }
      return null;
    }
  }

  /**
   * Config properties for a specific prerequisite check.
   */
  public static class PrerequisiteCheckProperties {
    /**
     * Prereq check name
     */
    @XmlAttribute
    public String name;

    /**
     * Config properties for the prerequisite check
     */
    @XmlElement(name="property")
    public List<PrerequisiteProperty> properties;

    /**
     * Get config properties as a map
     * @return Map of config properties
     */
    public Map<String, String> getProperties() {
      if(properties == null) {
        return null;
      }

      Map<String, String> result = new HashMap<>();
      for (PrerequisiteProperty property : properties) {
        result.put(property.name, property.value);
      }
      return result;
    }
  }

  /**
   * Prerequisite check config property
   */
  public static class PrerequisiteProperty {
    @XmlAttribute
    public String name;

    @XmlValue
    public String value;
  }

  /**
   * A {@code (pre|post)-downgrade} can have an attribute as well as contain {@code task} elements.
   */
  private static class DowngradeTasks {

    @XmlAttribute(name="copy-upgrade")
    private boolean copyUpgrade = false;

    @XmlElement(name="task")
    private List<Task> tasks = new ArrayList<>();
  }

  /**
   * @return true if this upgrade pack contains a group with a task that matches the given predicate
   */
  public boolean anyGroupTaskMatch(Predicate<Task> taskPredicate) {
    return getAllGroups().stream()
      .filter(ClusterGrouping.class::isInstance)
      .flatMap(group -> ((ClusterGrouping) group).executionStages.stream())
      .map(executeStage -> executeStage.task)
      .anyMatch(taskPredicate);
  }

  /**
   * Sets the stack id that owns this upgrade pack.  This may be set only once.
   *
   * @param stackId
   *          the owner stack id
   */
  public void setOwnerStackId(StackId stackId) {
    ownerStackId = (null == ownerStackId) ? stackId : ownerStackId;
  }

  /**
   * @return
   *      the stack id that owns this upgrade pack.
   */
  public StackId getOwnerStackId() {
    return ownerStackId;
  }

  /**
   * @return
   *      the class name used for orchestration options
   */
  public String getOrchestrationOptions() {
    return orchestrationOptionsClass;
  }

}
