public boolean equals(Object obj) {
   TransHopMeta other = (TransHopMeta) obj;
   if (this.from == null || this.to == null) {
     return false;
   }
   return this.from.equals(other.getFromStep()) && this.to.equals(other.getToStep());
 }
  public void redoTransformationAction(TransMeta transMeta, TransAction transAction) {
    switch (transAction.getType()) {
      case TransAction.TYPE_ACTION_NEW_STEP:
        // re-delete the step at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          StepMeta stepMeta = (StepMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addStep(idx, stepMeta);

          spoon.refreshTree();
          spoon.refreshGraph();
        }
        break;

      case TransAction.TYPE_ACTION_NEW_CONNECTION:
        // re-insert the connection at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          DatabaseMeta ci = (DatabaseMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addDatabase(idx, ci);
          spoon.refreshTree();
          spoon.refreshGraph();
        }
        break;

      case TransAction.TYPE_ACTION_NEW_NOTE:
        // re-insert the note at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          NotePadMeta ni = (NotePadMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addNote(idx, ni);
          spoon.refreshTree();
          spoon.refreshGraph();
        }
        break;

      case TransAction.TYPE_ACTION_NEW_HOP:
        // re-insert the hop at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          TransHopMeta hi = (TransHopMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addTransHop(idx, hi);
          spoon.refreshTree();
          spoon.refreshGraph();
        }
        break;

        //
        // DELETE
        //
      case TransAction.TYPE_ACTION_DELETE_STEP:
        // re-remove the step at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeStep(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

      case TransAction.TYPE_ACTION_DELETE_CONNECTION:
        // re-remove the connection at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeDatabase(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

      case TransAction.TYPE_ACTION_DELETE_NOTE:
        // re-remove the note at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeNote(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

      case TransAction.TYPE_ACTION_DELETE_HOP:
        // re-remove the hop at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeTransHop(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        //
        // CHANGE
        //

        // We changed a step : undo this...
      case TransAction.TYPE_ACTION_CHANGE_STEP:
        // Delete the current step, insert previous version.
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          StepMeta stepMeta = (StepMeta) ((StepMeta) transAction.getCurrent()[i]).clone();
          transMeta.getStep(transAction.getCurrentIndex()[i]).replaceMeta(stepMeta);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a connection : undo this...
      case TransAction.TYPE_ACTION_CHANGE_CONNECTION:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          DatabaseMeta databaseMeta = (DatabaseMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];

          transMeta.getDatabase(idx).replaceMeta((DatabaseMeta) databaseMeta.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a note : undo this...
      case TransAction.TYPE_ACTION_CHANGE_NOTE:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          NotePadMeta ni = (NotePadMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];

          transMeta.removeNote(idx);
          transMeta.addNote(idx, (NotePadMeta) ni.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a hop : undo this...
      case TransAction.TYPE_ACTION_CHANGE_HOP:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          TransHopMeta hi = (TransHopMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];

          transMeta.removeTransHop(idx);
          transMeta.addTransHop(idx, (TransHopMeta) hi.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        //
        // CHANGE POSITION
        //
      case TransAction.TYPE_ACTION_POSITION_STEP:
        for (int i = 0; i < transAction.getCurrentIndex().length; i++) {
          // Find & change the location of the step:
          StepMeta stepMeta = transMeta.getStep(transAction.getCurrentIndex()[i]);
          stepMeta.setLocation(transAction.getCurrentLocation()[i]);
        }
        spoon.refreshGraph();
        break;
      case TransAction.TYPE_ACTION_POSITION_NOTE:
        for (int i = 0; i < transAction.getCurrentIndex().length; i++) {
          int idx = transAction.getCurrentIndex()[i];
          NotePadMeta npi = transMeta.getNote(idx);
          Point curr = transAction.getCurrentLocation()[i];
          npi.setLocation(curr);
        }
        spoon.refreshGraph();
        break;
      default:
        break;
    }

    // OK, now check if we need to do this again...
    if (transMeta.viewNextUndo() != null) {
      if (transMeta.viewNextUndo().getNextAlso()) spoon.redoAction(transMeta);
    }
  }
  public void undoTransformationAction(TransMeta transMeta, TransAction transAction) {
    switch (transAction.getType()) {
        // We created a new step : undo this...
      case TransAction.TYPE_ACTION_NEW_STEP:
        // Delete the step at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeStep(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We created a new connection : undo this...
      case TransAction.TYPE_ACTION_NEW_CONNECTION:
        // Delete the connection at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeDatabase(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We created a new note : undo this...
      case TransAction.TYPE_ACTION_NEW_NOTE:
        // Delete the note at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeNote(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We created a new hop : undo this...
      case TransAction.TYPE_ACTION_NEW_HOP:
        // Delete the hop at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeTransHop(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We created a new slave : undo this...
      case TransAction.TYPE_ACTION_NEW_SLAVE:
        // Delete the slave at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.getSlaveServers().remove(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We created a new slave : undo this...
      case TransAction.TYPE_ACTION_NEW_CLUSTER:
        // Delete the slave at correct location:
        for (int i = transAction.getCurrent().length - 1; i >= 0; i--) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.getClusterSchemas().remove(idx);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        //
        // DELETE
        //

        // We delete a step : undo this...
      case TransAction.TYPE_ACTION_DELETE_STEP:
        // un-Delete the step at correct location: re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          StepMeta stepMeta = (StepMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addStep(idx, stepMeta);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We deleted a connection : undo this...
      case TransAction.TYPE_ACTION_DELETE_CONNECTION:
        // re-insert the connection at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          DatabaseMeta ci = (DatabaseMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addDatabase(idx, ci);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We delete new note : undo this...
      case TransAction.TYPE_ACTION_DELETE_NOTE:
        // re-insert the note at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          NotePadMeta ni = (NotePadMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          transMeta.addNote(idx, ni);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We deleted a hop : undo this...
      case TransAction.TYPE_ACTION_DELETE_HOP:
        // re-insert the hop at correct location:
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          TransHopMeta hi = (TransHopMeta) transAction.getCurrent()[i];
          int idx = transAction.getCurrentIndex()[i];
          // Build a new hop:
          StepMeta from = transMeta.findStep(hi.getFromStep().getName());
          StepMeta to = transMeta.findStep(hi.getToStep().getName());
          TransHopMeta hinew = new TransHopMeta(from, to);
          transMeta.addTransHop(idx, hinew);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        //
        // CHANGE
        //

        // We changed a step : undo this...
      case TransAction.TYPE_ACTION_CHANGE_STEP:
        // Delete the current step, insert previous version.
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          StepMeta prev = (StepMeta) ((StepMeta) transAction.getPrevious()[i]).clone();
          int idx = transAction.getCurrentIndex()[i];

          transMeta.getStep(idx).replaceMeta(prev);
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a connection : undo this...
      case TransAction.TYPE_ACTION_CHANGE_CONNECTION:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          DatabaseMeta prev = (DatabaseMeta) transAction.getPrevious()[i];
          int idx = transAction.getCurrentIndex()[i];

          transMeta.getDatabase(idx).replaceMeta((DatabaseMeta) prev.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a note : undo this...
      case TransAction.TYPE_ACTION_CHANGE_NOTE:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          int idx = transAction.getCurrentIndex()[i];
          transMeta.removeNote(idx);
          NotePadMeta prev = (NotePadMeta) transAction.getPrevious()[i];
          transMeta.addNote(idx, (NotePadMeta) prev.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        // We changed a hop : undo this...
      case TransAction.TYPE_ACTION_CHANGE_HOP:
        // Delete & re-insert
        for (int i = 0; i < transAction.getCurrent().length; i++) {
          TransHopMeta prev = (TransHopMeta) transAction.getPrevious()[i];
          int idx = transAction.getCurrentIndex()[i];

          transMeta.removeTransHop(idx);
          transMeta.addTransHop(idx, (TransHopMeta) prev.clone());
        }
        spoon.refreshTree();
        spoon.refreshGraph();
        break;

        //
        // POSITION
        //

        // The position of a step has changed: undo this...
      case TransAction.TYPE_ACTION_POSITION_STEP:
        // Find the location of the step:
        for (int i = 0; i < transAction.getCurrentIndex().length; i++) {
          StepMeta stepMeta = transMeta.getStep(transAction.getCurrentIndex()[i]);
          stepMeta.setLocation(transAction.getPreviousLocation()[i]);
        }
        spoon.refreshGraph();
        break;

        // The position of a note has changed: undo this...
      case TransAction.TYPE_ACTION_POSITION_NOTE:
        for (int i = 0; i < transAction.getCurrentIndex().length; i++) {
          int idx = transAction.getCurrentIndex()[i];
          NotePadMeta npi = transMeta.getNote(idx);
          Point prev = transAction.getPreviousLocation()[i];
          npi.setLocation(prev);
        }
        spoon.refreshGraph();
        break;
      default:
        break;
    }

    // OK, now check if we need to do this again...
    if (transMeta.viewNextUndo() != null) {
      if (transMeta.viewNextUndo().getNextAlso()) spoon.undoAction(transMeta);
    }
  }
 /** Compare 2 hops. */
 public int compareTo(TransHopMeta obj) {
   return toString().compareTo(obj.toString());
 }
  public DataNode elementToDataNode(final RepositoryElementInterface element)
      throws KettleException {
    TransMeta transMeta = (TransMeta) element;

    DataNode rootNode = new DataNode(NODE_TRANS);

    DataNode stepsNode = rootNode.addNode(NODE_STEPS);

    if (transMeta.getPrivateTransformationDatabases() != null) {
      // save all private transformations database name http://jira.pentaho.com/browse/PPP-3405
      DataNode privateDatabaseNode = rootNode.addNode(NODE_TRANS_PRIVATE_DATABASES);
      for (String privateDatabase : transMeta.getPrivateTransformationDatabases()) {
        privateDatabaseNode.addNode(privateDatabase);
      }
    }
    // Also save all the steps in the transformation!
    //
    int stepNr = 0;
    for (StepMeta step : transMeta.getSteps()) {
      stepNr++;
      DataNode stepNode =
          stepsNode.addNode(
              sanitizeNodeName(step.getName()) + "_" + stepNr + EXT_STEP); // $NON-NLS-1$

      // Store the main data
      //
      stepNode.setProperty(PROP_NAME, step.getName());
      stepNode.setProperty(PROP_DESCRIPTION, step.getDescription());
      stepNode.setProperty(PROP_STEP_TYPE, step.getStepID());
      stepNode.setProperty(PROP_STEP_DISTRIBUTE, step.isDistributes());
      stepNode.setProperty(
          PROP_STEP_ROW_DISTRIBUTION,
          step.getRowDistribution() == null ? null : step.getRowDistribution().getCode());
      stepNode.setProperty(PROP_STEP_COPIES, step.getCopies());
      stepNode.setProperty(PROP_STEP_COPIES_STRING, step.getCopiesString());
      stepNode.setProperty(PROP_STEP_GUI_LOCATION_X, step.getLocation().x);
      stepNode.setProperty(PROP_STEP_GUI_LOCATION_Y, step.getLocation().y);
      stepNode.setProperty(PROP_STEP_GUI_DRAW, step.isDrawn());

      // Also save the step group attributes map
      //
      AttributesMapUtil.saveAttributesMap(stepNode, step);

      // Save the step metadata using the repository save method, NOT XML
      // That is because we want to keep the links to databases, conditions, etc by ID, not name.
      //
      StepMetaInterface stepMetaInterface = step.getStepMetaInterface();
      DataNode stepCustomNode = new DataNode(NODE_STEP_CUSTOM);
      Repository proxy = new RepositoryProxy(stepCustomNode);
      compatibleSaveRep(stepMetaInterface, proxy, null, null);
      stepMetaInterface.saveRep(proxy, proxy.getMetaStore(), null, null);
      stepNode.addNode(stepCustomNode);

      // Save the partitioning information by reference as well...
      //
      StepPartitioningMeta partitioningMeta = step.getStepPartitioningMeta();
      if (partitioningMeta != null
          && partitioningMeta.getPartitionSchema() != null
          && partitioningMeta.isPartitioned()) {
        DataNodeRef ref =
            new DataNodeRef(partitioningMeta.getPartitionSchema().getObjectId().getId());
        stepNode.setProperty(PROP_PARTITIONING_SCHEMA, ref);
        stepNode.setProperty(
            PROP_PARTITIONING_METHOD, partitioningMeta.getMethodCode()); // method of partitioning
        if (partitioningMeta.getPartitioner() != null) {
          DataNode partitionerCustomNode = new DataNode(NODE_PARTITIONER_CUSTOM);
          proxy = new RepositoryProxy(partitionerCustomNode);
          partitioningMeta.getPartitioner().saveRep(proxy, null, null);
          stepNode.addNode(partitionerCustomNode);
        }
      }

      // Save the clustering information as well...
      //
      stepNode.setProperty(
          PROP_CLUSTER_SCHEMA,
          step.getClusterSchema() == null
              ? ""
              : step.getClusterSchema() //$NON-NLS-1$
                  .getName());

      // Save the error hop metadata
      //
      StepErrorMeta stepErrorMeta = step.getStepErrorMeta();
      if (stepErrorMeta != null) {
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_SOURCE_STEP,
            stepErrorMeta.getSourceStep() != null
                ? stepErrorMeta.getSourceStep().getName()
                : ""); //$NON-NLS-1$
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_TARGET_STEP,
            stepErrorMeta.getTargetStep() != null
                ? stepErrorMeta.getTargetStep().getName()
                : ""); //$NON-NLS-1$
        stepNode.setProperty(PROP_STEP_ERROR_HANDLING_IS_ENABLED, stepErrorMeta.isEnabled());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_NR_VALUENAME, stepErrorMeta.getNrErrorsValuename());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_DESCRIPTIONS_VALUENAME,
            stepErrorMeta.getErrorDescriptionsValuename());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_FIELDS_VALUENAME, stepErrorMeta.getErrorFieldsValuename());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_CODES_VALUENAME, stepErrorMeta.getErrorCodesValuename());
        stepNode.setProperty(PROP_STEP_ERROR_HANDLING_MAX_ERRORS, stepErrorMeta.getMaxErrors());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_MAX_PCT_ERRORS, stepErrorMeta.getMaxPercentErrors());
        stepNode.setProperty(
            PROP_STEP_ERROR_HANDLING_MIN_PCT_ROWS, stepErrorMeta.getMinPercentRows());
      }
    }

    // Save the notes
    //
    DataNode notesNode = rootNode.addNode(NODE_NOTES);
    notesNode.setProperty(PROP_NR_NOTES, transMeta.nrNotes());
    for (int i = 0; i < transMeta.nrNotes(); i++) {
      NotePadMeta note = transMeta.getNote(i);
      DataNode noteNode = notesNode.addNode(NOTE_PREFIX + i);

      noteNode.setProperty(PROP_XML, note.getXML());
    }

    // Finally, save the hops
    //
    DataNode hopsNode = rootNode.addNode(NODE_HOPS);
    hopsNode.setProperty(PROP_NR_HOPS, transMeta.nrTransHops());
    for (int i = 0; i < transMeta.nrTransHops(); i++) {
      TransHopMeta hop = transMeta.getTransHop(i);
      DataNode hopNode = hopsNode.addNode(TRANS_HOP_PREFIX + i);
      hopNode.setProperty(TRANS_HOP_FROM, hop.getFromStep().getName());
      hopNode.setProperty(TRANS_HOP_TO, hop.getToStep().getName());
      hopNode.setProperty(TRANS_HOP_ENABLED, hop.isEnabled());
    }

    // Parameters
    //
    String[] paramKeys = transMeta.listParameters();
    DataNode paramsNode = rootNode.addNode(NODE_PARAMETERS);
    paramsNode.setProperty(PROP_NR_PARAMETERS, paramKeys == null ? 0 : paramKeys.length);

    for (int idx = 0; idx < paramKeys.length; idx++) {
      DataNode paramNode = paramsNode.addNode(TRANS_PARAM_PREFIX + idx);
      String key = paramKeys[idx];
      String description = transMeta.getParameterDescription(paramKeys[idx]);
      String defaultValue = transMeta.getParameterDefault(paramKeys[idx]);

      paramNode.setProperty(PARAM_KEY, key != null ? key : ""); // $NON-NLS-1$
      paramNode.setProperty(PARAM_DEFAULT, defaultValue != null ? defaultValue : ""); // $NON-NLS-1$
      paramNode.setProperty(PARAM_DESC, description != null ? description : ""); // $NON-NLS-1$
    }

    // Let's not forget to save the details of the transformation itself.
    // This includes logging information, parameters, etc.
    //
    saveTransformationDetails(rootNode, transMeta);

    return rootNode;
  }
  @Override
  public String encode(AbstractMeta meta) throws Exception {
    TransMeta transMeta = (TransMeta) meta;

    mxGraph graph = new mxGraph();
    graph.getModel().beginUpdate();
    mxCell parent = (mxCell) graph.getDefaultParent();
    Document doc = mxUtils.createDocument();

    try {
      Element e = super.encodeCommRootAttr(transMeta, doc);
      e.setAttribute("trans_version", transMeta.getTransversion());
      e.setAttribute("trans_type", transMeta.getTransformationType().getCode());
      e.setAttribute("trans_status", String.valueOf(transMeta.getTransstatus()));

      // variables
      Properties sp = new Properties();
      JSONArray jsonArray = new JSONArray();

      String[] keys = Variables.getADefaultVariableSpace().listVariables();
      for (int i = 0; i < keys.length; i++) {
        sp.put(keys[i], Variables.getADefaultVariableSpace().getVariable(keys[i]));
      }

      List<String> vars = transMeta.getUsedVariables();
      for (int i = 0; i < vars.size(); i++) {
        String varname = vars.get(i);
        if (!varname.startsWith(Const.INTERNAL_VARIABLE_PREFIX)
            && Const.indexOfString(varname, transMeta.listParameters()) < 0) {
          JSONObject param = new JSONObject();
          param.put("var_name", varname);
          param.put("var_value", sp.getProperty(varname, ""));
          jsonArray.add(param);
        }
      }

      for (String varname : Const.INTERNAL_JOB_VARIABLES) {
        String value = transMeta.getVariable(varname);
        if (!Const.isEmpty(value)) {

          JSONObject param = new JSONObject();
          param.put("var_name", varname);
          param.put("var_value", value);
          jsonArray.add(param);
        }
      }
      e.setAttribute("variables", jsonArray.toString());

      TransLogTable transLogTable = transMeta.getTransLogTable();
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("connection", transLogTable.getConnectionName());
      jsonObject.put("schema", transLogTable.getSchemaName());
      jsonObject.put("table", transLogTable.getTableName());
      jsonObject.put("size_limit_lines", transLogTable.getLogSizeLimit());
      jsonObject.put("interval", transLogTable.getLogInterval());
      jsonObject.put("timeout_days", transLogTable.getTimeoutInDays());
      JSONArray fields = new JSONArray();
      for (LogTableField field : transLogTable.getFields()) {
        JSONObject jsonField = new JSONObject();
        jsonField.put("id", field.getId());
        jsonField.put("enabled", field.isEnabled());
        jsonField.put("name", field.getFieldName());
        jsonField.put("subjectAllowed", field.isSubjectAllowed());
        if (field.isSubjectAllowed()) {
          jsonField.put("subject", field.getSubject() == null ? "" : field.getSubject().toString());
        } else {
          jsonField.put("subject", "-");
        }
        jsonField.put("description", StringEscapeHelper.encode(field.getDescription()));
        fields.add(jsonField);
      }
      jsonObject.put("fields", fields);
      e.setAttribute("transLogTable", jsonObject.toString());

      StepLogTable stepLogTable = transMeta.getStepLogTable();
      jsonObject = new JSONObject();
      jsonObject.put("connection", stepLogTable.getConnectionName());
      jsonObject.put("schema", stepLogTable.getSchemaName());
      jsonObject.put("table", stepLogTable.getTableName());
      jsonObject.put("timeout_days", stepLogTable.getTimeoutInDays());
      fields = new JSONArray();
      for (LogTableField field : stepLogTable.getFields()) {
        JSONObject jsonField = new JSONObject();
        jsonField.put("id", field.getId());
        jsonField.put("enabled", field.isEnabled());
        jsonField.put("name", field.getFieldName());
        jsonField.put("description", StringEscapeHelper.encode(field.getDescription()));
        fields.add(jsonField);
      }
      jsonObject.put("fields", fields);
      e.setAttribute("stepLogTable", jsonObject.toString());

      PerformanceLogTable performanceLogTable = transMeta.getPerformanceLogTable();
      jsonObject = new JSONObject();
      jsonObject.put("connection", performanceLogTable.getConnectionName());
      jsonObject.put("schema", performanceLogTable.getSchemaName());
      jsonObject.put("table", performanceLogTable.getTableName());
      jsonObject.put("interval", performanceLogTable.getLogInterval());
      jsonObject.put("timeout_days", performanceLogTable.getTimeoutInDays());
      fields = new JSONArray();
      for (LogTableField field : performanceLogTable.getFields()) {
        JSONObject jsonField = new JSONObject();
        jsonField.put("id", field.getId());
        jsonField.put("enabled", field.isEnabled());
        jsonField.put("name", field.getFieldName());
        jsonField.put("description", StringEscapeHelper.encode(field.getDescription()));
        fields.add(jsonField);
      }
      jsonObject.put("fields", fields);
      e.setAttribute("performanceLogTable", jsonObject.toString());

      MetricsLogTable metricsLogTable = transMeta.getMetricsLogTable();
      jsonObject = new JSONObject();
      jsonObject.put("connection", metricsLogTable.getConnectionName());
      jsonObject.put("schema", metricsLogTable.getSchemaName());
      jsonObject.put("table", metricsLogTable.getTableName());
      jsonObject.put("timeout_days", metricsLogTable.getTimeoutInDays());
      fields = new JSONArray();
      for (LogTableField field : metricsLogTable.getFields()) {
        JSONObject jsonField = new JSONObject();
        jsonField.put("id", field.getId());
        jsonField.put("enabled", field.isEnabled());
        jsonField.put("name", field.getFieldName());
        jsonField.put("description", StringEscapeHelper.encode(field.getDescription()));
        fields.add(jsonField);
      }
      jsonObject.put("fields", fields);
      e.setAttribute("metricsLogTable", jsonObject.toString());

      jsonObject = new JSONObject();
      jsonObject.put(
          "connection",
          transMeta.getMaxDateConnection() == null
              ? ""
              : transMeta.getMaxDateConnection().getName());
      jsonObject.put("table", transMeta.getMaxDateTable());
      jsonObject.put("field", transMeta.getMaxDateField());
      jsonObject.put("offset", transMeta.getMaxDateOffset());
      jsonObject.put("maxdiff", transMeta.getMaxDateDifference());
      e.setAttribute("maxdate", jsonObject.toString());

      e.setAttribute("size_rowset", String.valueOf(transMeta.getSizeRowset()));
      e.setAttribute("sleep_time_empty", String.valueOf(transMeta.getSleepTimeEmpty()));
      e.setAttribute("sleep_time_full", String.valueOf(transMeta.getSleepTimeFull()));
      e.setAttribute("unique_connections", transMeta.isUsingUniqueConnections() ? "Y" : "N");
      e.setAttribute("feedback_shown", transMeta.isFeedbackShown() ? "Y" : "N");
      e.setAttribute("feedback_size", String.valueOf(transMeta.getFeedbackSize()));
      e.setAttribute(
          "using_thread_priorities", transMeta.isUsingThreadPriorityManagment() ? "Y" : "N");
      e.setAttribute(
          "capture_step_performance", transMeta.isCapturingStepPerformanceSnapShots() ? "Y" : "N");
      e.setAttribute(
          "step_performance_capturing_delay",
          String.valueOf(transMeta.getStepPerformanceCapturingDelay()));
      e.setAttribute(
          "step_performance_capturing_size_limit",
          transMeta.getStepPerformanceCapturingSizeLimit());

      super.encodeSlaveServers(e, transMeta);
      encodeClusterSchema(e, transMeta);
      encodePartitionSchema(e, transMeta);

      try {
        if (transMeta.getKey() != null) {
          e.setAttribute("key_for_session_key", XMLHandler.encodeBinaryData(transMeta.getKey()));
        } else {
          e.setAttribute("key_for_session_key", "");
        }
      } catch (Exception e1) {
        e1.printStackTrace();
        e.setAttribute("key_for_session_key", "");
      }
      e.setAttribute("is_key_private", transMeta.isPrivateKey() ? "Y" : "N");

      super.encodeNote(doc, graph, transMeta);

      super.encodeDatabases(e, transMeta);
      parent.setValue(e);

      // encode steps and hops
      HashMap<StepMeta, Object> cells = new HashMap<StepMeta, Object>();
      List<StepMeta> list = transMeta.getSteps();
      for (int i = 0; i < list.size(); i++) {
        StepMeta step = (StepMeta) list.get(i);
        Point p = step.getLocation();
        StepEncoder stepEncoder = (StepEncoder) PluginFactory.getBean(step.getStepID());

        PluginInterface plugin =
            PluginRegistry.getInstance().getPlugin(StepPluginType.class, step.getStepID());
        Object cell =
            graph.insertVertex(
                parent,
                null,
                stepEncoder.encodeStep(step),
                p.x,
                p.y,
                40,
                40,
                "icon;image=" + SvgImageUrl.getUrl(plugin));
        cells.put(step, cell);
      }

      for (int i = 0; i < transMeta.nrTransHops(); i++) {
        TransHopMeta transHopMeta = transMeta.getTransHop(i);

        Object v1 = cells.get(transHopMeta.getFromStep());
        Object v2 = cells.get(transHopMeta.getToStep());

        graph.insertEdge(parent, null, TransHopMetaCodec.encode(transHopMeta), v1, v2);
      }
    } finally {
      graph.getModel().endUpdate();
    }

    mxCodec codec = new mxCodec();
    return mxUtils.getPrettyXml(codec.encode(graph.getModel()));
  }
  @Override
  public AbstractMeta decode(String graphXml) throws Exception {
    mxGraph graph = new mxGraph();
    mxCodec codec = new mxCodec();
    Document doc = mxUtils.parseXml(graphXml);
    codec.decode(doc.getDocumentElement(), graph.getModel());
    mxCell root = (mxCell) graph.getDefaultParent();

    TransMeta transMeta = new TransMeta();
    decodeCommRootAttr(root, transMeta);
    transMeta.setTransstatus(Const.toInt(root.getAttribute("trans_status"), -1));
    transMeta.setTransversion(root.getAttribute("trans_version"));

    if (transMeta.getRepository() != null)
      transMeta.setSharedObjects(transMeta.getRepository().readTransSharedObjects(transMeta));
    else transMeta.setSharedObjects(transMeta.readSharedObjects());

    transMeta.importFromMetaStore();

    decodeDatabases(root, transMeta);
    decodeNote(graph, transMeta);

    int count = graph.getModel().getChildCount(root);
    for (int i = 0; i < count; i++) {
      mxCell cell = (mxCell) graph.getModel().getChildAt(root, i);
      if (cell.isVertex()) {
        Element e = (Element) cell.getValue();
        if (PropsUI.TRANS_STEP_NAME.equals(e.getTagName())) {
          StepDecoder stepDecoder = (StepDecoder) PluginFactory.getBean(cell.getAttribute("ctype"));
          StepMeta stepMeta =
              stepDecoder.decodeStep(cell, transMeta.getDatabases(), transMeta.getMetaStore());
          stepMeta.setParentTransMeta(transMeta);
          if (stepMeta.isMissing()) {
            transMeta.addMissingTrans((MissingTrans) stepMeta.getStepMetaInterface());
          }

          StepMeta check = transMeta.findStep(stepMeta.getName());
          if (check != null) {
            if (!check.isShared()) {
              // Don't overwrite shared objects
              transMeta.addOrReplaceStep(stepMeta);
            } else {
              check.setDraw(stepMeta.isDrawn()); // Just keep the  drawn flag  and location
              check.setLocation(stepMeta.getLocation());
            }
          } else {
            transMeta.addStep(stepMeta); // simply add it.
          }
        }
      }
    }

    // Have all StreamValueLookups, etc. reference the correct source steps...
    //
    for (int i = 0; i < transMeta.nrSteps(); i++) {
      StepMeta stepMeta = transMeta.getStep(i);
      StepMetaInterface sii = stepMeta.getStepMetaInterface();
      if (sii != null) {
        sii.searchInfoAndTargetSteps(transMeta.getSteps());
      }
    }

    count = graph.getModel().getChildCount(root);
    for (int i = 0; i < count; i++) {
      mxCell cell = (mxCell) graph.getModel().getChildAt(root, i);
      if (cell.isEdge()) {
        mxCell source = (mxCell) cell.getSource();
        mxCell target = (mxCell) cell.getTarget();

        TransHopMeta hopinf = new TransHopMeta(null, null, true);
        String[] stepNames = transMeta.getStepNames();
        for (int j = 0; j < stepNames.length; j++) {
          if (stepNames[j].equalsIgnoreCase(source.getAttribute("label")))
            hopinf.setFromStep(transMeta.getStep(j));
          if (stepNames[j].equalsIgnoreCase(target.getAttribute("label")))
            hopinf.setToStep(transMeta.getStep(j));
        }
        transMeta.addTransHop(hopinf);
      }
    }

    JSONObject jsonObject = JSONObject.fromObject(root.getAttribute("transLogTable"));
    TransLogTable transLogTable = transMeta.getTransLogTable();
    transLogTable.setConnectionName(jsonObject.optString("connection"));
    transLogTable.setSchemaName(jsonObject.optString("schema"));
    transLogTable.setTableName(jsonObject.optString("table"));
    transLogTable.setLogSizeLimit(jsonObject.optString("size_limit_lines"));
    transLogTable.setLogInterval(jsonObject.optString("interval"));
    transLogTable.setTimeoutInDays(jsonObject.optString("timeout_days"));
    JSONArray jsonArray = jsonObject.optJSONArray("fields");
    if (jsonArray != null) {
      for (int i = 0; i < jsonArray.size(); i++) {
        JSONObject fieldJson = jsonArray.getJSONObject(i);
        String id = fieldJson.optString("id");
        LogTableField field = transLogTable.findField(id);
        if (field == null) {
          field = transLogTable.getFields().get(i);
        }
        if (field != null) {
          field.setFieldName(fieldJson.optString("name"));
          field.setEnabled(fieldJson.optBoolean("enabled"));
          field.setSubject(StepMeta.findStep(transMeta.getSteps(), fieldJson.optString("subject")));
        }
      }
    }

    jsonObject = JSONObject.fromObject(root.getAttribute("stepLogTable"));
    StepLogTable stepLogTable = transMeta.getStepLogTable();
    stepLogTable.setConnectionName(jsonObject.optString("connection"));
    stepLogTable.setSchemaName(jsonObject.optString("schema"));
    stepLogTable.setTableName(jsonObject.optString("table"));
    stepLogTable.setTimeoutInDays(jsonObject.optString("timeout_days"));
    jsonArray = jsonObject.optJSONArray("fields");
    if (jsonArray != null) {
      for (int i = 0; i < jsonArray.size(); i++) {
        JSONObject fieldJson = jsonArray.getJSONObject(i);
        String id = fieldJson.optString("id");
        LogTableField field = stepLogTable.findField(id);
        if (field == null && i < stepLogTable.getFields().size()) {
          field = stepLogTable.getFields().get(i);
        }
        if (field != null) {
          field.setFieldName(fieldJson.optString("name"));
          field.setEnabled(fieldJson.optBoolean("enabled"));
        }
      }
    }

    jsonObject = JSONObject.fromObject(root.getAttribute("performanceLogTable"));
    PerformanceLogTable performanceLogTable = transMeta.getPerformanceLogTable();
    performanceLogTable.setConnectionName(jsonObject.optString("connection"));
    performanceLogTable.setSchemaName(jsonObject.optString("schema"));
    performanceLogTable.setTableName(jsonObject.optString("table"));
    performanceLogTable.setLogInterval(jsonObject.optString("interval"));
    performanceLogTable.setTimeoutInDays(jsonObject.optString("timeout_days"));
    jsonArray = jsonObject.optJSONArray("fields");
    if (jsonArray != null) {
      for (int i = 0; i < jsonArray.size(); i++) {
        JSONObject fieldJson = jsonArray.getJSONObject(i);
        String id = fieldJson.optString("id");
        LogTableField field = performanceLogTable.findField(id);
        if (field == null && i < performanceLogTable.getFields().size()) {
          field = performanceLogTable.getFields().get(i);
        }
        if (field != null) {
          field.setFieldName(fieldJson.optString("name"));
          field.setEnabled(fieldJson.optBoolean("enabled"));
        }
      }
    }

    jsonObject = JSONObject.fromObject(root.getAttribute("metricsLogTable"));
    MetricsLogTable metricsLogTable = transMeta.getMetricsLogTable();
    metricsLogTable.setConnectionName(jsonObject.optString("connection"));
    metricsLogTable.setSchemaName(jsonObject.optString("schema"));
    metricsLogTable.setTableName(jsonObject.optString("table"));
    metricsLogTable.setTimeoutInDays(jsonObject.optString("timeout_days"));
    jsonArray = jsonObject.optJSONArray("fields");
    if (jsonArray != null) {
      for (int i = 0; i < jsonArray.size(); i++) {
        JSONObject fieldJson = jsonArray.getJSONObject(i);
        String id = fieldJson.optString("id");
        LogTableField field = metricsLogTable.findField(id);
        if (field == null && i < metricsLogTable.getFields().size()) {
          field = metricsLogTable.getFields().get(i);
        }
        if (field != null) {
          field.setFieldName(fieldJson.optString("name"));
          field.setEnabled(fieldJson.optBoolean("enabled"));
        }
      }
    }

    jsonArray = JSONArray.fromObject(root.getAttribute("partitionschemas"));
    for (int i = 0; i < jsonArray.size(); i++) {
      jsonObject = jsonArray.getJSONObject(i);
      PartitionSchema partitionSchema = decodePartitionSchema(jsonObject);
      PartitionSchema check = transMeta.findPartitionSchema(partitionSchema.getName());
      if (check != null) {
        if (!check.isShared()) {
          transMeta.addOrReplacePartitionSchema(partitionSchema);
        }
      } else {
        transMeta.getPartitionSchemas().add(partitionSchema);
      }
    }

    decodeSlaveServers(root, transMeta);

    jsonArray = JSONArray.fromObject(root.getAttribute("clusterSchemas"));
    for (int i = 0; i < jsonArray.size(); i++) {
      jsonObject = jsonArray.getJSONObject(i);
      ClusterSchema clusterSchema = decodeClusterSchema(jsonObject, transMeta.getSlaveServers());
      clusterSchema.shareVariablesWith(transMeta);

      ClusterSchema check = transMeta.findClusterSchema(clusterSchema.getName());
      if (check != null) {
        if (!check.isShared()) {
          transMeta.addOrReplaceClusterSchema(clusterSchema);
        }
      } else {
        transMeta.getClusterSchemas().add(clusterSchema);
      }
    }

    for (int i = 0; i < transMeta.nrSteps(); i++) {
      transMeta.getStep(i).setClusterSchemaAfterLoading(transMeta.getClusterSchemas());
    }

    transMeta.setSizeRowset(Const.toInt(root.getAttribute("size_rowset"), Const.ROWS_IN_ROWSET));
    transMeta.setSleepTimeEmpty(
        Const.toInt(root.getAttribute("sleep_time_empty"), Const.TIMEOUT_GET_MILLIS));
    transMeta.setSleepTimeFull(
        Const.toInt(root.getAttribute("sleep_time_full"), Const.TIMEOUT_PUT_MILLIS));
    transMeta.setUsingUniqueConnections(
        "Y".equalsIgnoreCase(root.getAttribute("unique_connections")));

    transMeta.setFeedbackShown(!"N".equalsIgnoreCase(root.getAttribute("feedback_shown")));
    transMeta.setFeedbackSize(Const.toInt(root.getAttribute("feedback_size"), Const.ROWS_UPDATE));
    transMeta.setUsingThreadPriorityManagment(
        !"N".equalsIgnoreCase(root.getAttribute("using_thread_priorities")));

    transMeta.setCapturingStepPerformanceSnapShots(
        "Y".equalsIgnoreCase(root.getAttribute("capture_step_performance")));
    transMeta.setStepPerformanceCapturingDelay(
        Const.toLong(root.getAttribute("step_performance_capturing_delay"), 1000));
    transMeta.setStepPerformanceCapturingSizeLimit(
        root.getAttribute("step_performance_capturing_size_limit"));

    transMeta.setKey(XMLHandler.stringToBinary(root.getAttribute("key_for_session_key")));
    transMeta.setPrivateKey("Y".equals(root.getAttribute("is_key_private")));

    return transMeta;
  }