@Override
  public void readRep(
      Repository rep, IMetaStore metaStore, ObjectId id_step, List<DatabaseMeta> databases)
      throws KettleException {

    ModelAnnotationGroup modelAnnotationGroup = new ModelAnnotationGroup();
    try {

      setModelAnnotationCategory(rep.getStepAttributeString(id_step, "CATEGORY_NAME"));
      setTargetOutputStep(rep.getStepAttributeString(id_step, "TARGET_OUTPUT_STEP"));

      int nrAnnotations = rep.countNrStepAttributes(id_step, "ANNOTATION_NAME");

      // Read annotations
      for (int i = 0; i < nrAnnotations; i++) {
        String annotationName = rep.getStepAttributeString(id_step, i, "ANNOTATION_NAME");
        String annotationFieldName =
            rep.getStepAttributeString(id_step, i, "ANNOTATION_FIELD_NAME");
        String annotationType = rep.getStepAttributeString(id_step, i, "ANNOTATION_TYPE");

        // Create model annotation
        ModelAnnotation<?> modelAnnotation =
            ModelAnnotationGroupXmlReader.create(annotationType, annotationFieldName);
        if (StringUtils.isNotBlank(annotationName)) {
          modelAnnotation.setName(annotationName);
        }

        if (StringUtils.isNotBlank(annotationType)) {
          // Populate annotation properties
          Map<String, Serializable> map = new HashMap<String, Serializable>();
          for (String key : modelAnnotation.getAnnotation().getModelPropertyIds()) {
            try {
              String value = rep.getStepAttributeString(id_step, i, "PROPERTY_VALUE_" + key);
              if (StringUtils.isNotBlank(value)) {
                map.put(key, value);
              }
            } catch (KettleException ke) {
              // Ignore - not found
            }
          }
          modelAnnotation.populateAnnotation(map);
        }

        // Add to group
        modelAnnotationGroup.add(modelAnnotation);
      }

      modelAnnotationGroup.setSharedDimension(
          BooleanUtils.toBoolean(rep.getStepAttributeString(id_step, "SHARED_DIMENSION")));
      sharedDimension = modelAnnotationGroup.isSharedDimension();
      modelAnnotationGroup.setDescription(rep.getStepAttributeString(id_step, "DESCRIPTION"));

      List<DataProvider> dataProviders = new ArrayList<DataProvider>();
      int nrDataProviders = rep.countNrStepAttributes(id_step, "DP_NAME");
      for (int i = 0; i < nrDataProviders; i++) {

        DataProvider dataProvider = new DataProvider();

        dataProvider.setName(rep.getStepAttributeString(id_step, i, "DP_NAME"));
        dataProvider.setSchemaName(rep.getStepAttributeString(id_step, i, "DP_SCHEMA_NAME"));
        dataProvider.setTableName(rep.getStepAttributeString(id_step, i, "DP_TABLE_NAME"));
        dataProvider.setDatabaseMetaNameRef(
            rep.getStepAttributeString(id_step, i, "DP_DATABASE_META_NAME_REF"));

        List<ColumnMapping> columnMappings = new ArrayList<ColumnMapping>();
        long nrColumnMappings =
            rep.getStepAttributeString(id_step, "CM_COUNT_" + i) != null
                ? Long.valueOf(rep.getStepAttributeString(id_step, "CM_COUNT_" + i))
                : 0;
        for (int j = 0; j < nrColumnMappings; j++) {

          ColumnMapping columnMapping = new ColumnMapping();

          columnMapping.setName(rep.getStepAttributeString(id_step, i, "CM_NAME_" + j));
          columnMapping.setColumnName(
              rep.getStepAttributeString(id_step, i, "CM_COLUMN_NAME_" + j));
          String dataType = rep.getStepAttributeString(id_step, i, "CM_DATA_TYPE_" + j);
          if (StringUtils.isNotBlank(dataType)) {
            columnMapping.setColumnDataType(DataType.valueOf(dataType));
          }

          columnMappings.add(columnMapping);
        }

        dataProvider.setColumnMappings(columnMappings);
        dataProviders.add(dataProvider);
      }
      modelAnnotationGroup.setDataProviders(dataProviders);

    } catch (Exception e) {
      throw new KettleException(
          BaseMessages.getString(
              PKG, "ModelAnnotationMeta.Exception.UnexpectedErrorReadingStepInfoFromRepository"),
          e);
    }

    setModelAnnotations(modelAnnotationGroup);

    // This may override the loaded model annotation group
    if (StringUtils.isNotBlank(getModelAnnotationCategory())) {
      readDataFromMetaStore(metaStore);
    }
  }
  @Override
  public void saveRep(
      final Repository rep,
      final IMetaStore metaStore,
      final ObjectId id_transformation,
      final ObjectId id_step)
      throws KettleException {

    try {

      rep.saveStepAttribute(
          id_transformation, id_step, "CATEGORY_NAME", getModelAnnotationCategory());
      rep.saveStepAttribute(
          id_transformation, id_step, "TARGET_OUTPUT_STEP", getTargetOutputStep());

      // Save model annotations
      if (getModelAnnotations() != null) {

        for (int i = 0; i < getModelAnnotations().size(); i++) {

          final ModelAnnotation<?> modelAnnotation = getModelAnnotations().get(i);

          // Add default name
          if (StringUtils.isBlank(modelAnnotation.getName())) {
            modelAnnotation.setName(UUID.randomUUID().toString()); // backwards compatibility
          }

          rep.saveStepAttribute(
              id_transformation, id_step, i, "ANNOTATION_NAME", modelAnnotation.getName());
          rep.saveStepAttribute(
              id_transformation,
              id_step,
              i,
              "ANNOTATION_FIELD_NAME",
              modelAnnotation.getAnnotation().getField());

          if (modelAnnotation.getType() != null) {
            rep.saveStepAttribute(
                id_transformation,
                id_step,
                i,
                "ANNOTATION_TYPE",
                modelAnnotation.getType().toString());

            final int INDEX = i; // trap index so we can use in inner class
            modelAnnotation.iterateProperties(
                new KeyValueClosure() {
                  @Override
                  public void execute(String key, Serializable serializable) {
                    try {
                      if (serializable != null && StringUtils.isNotBlank(serializable.toString())) {
                        rep.saveStepAttribute(
                            id_transformation,
                            id_step,
                            INDEX,
                            "PROPERTY_VALUE_" + key,
                            serializable.toString());
                      }
                    } catch (KettleException e) {
                      logError(e.getMessage());
                    }
                  }
                });
          }
        }

        rep.saveStepAttribute(
            id_transformation,
            id_step,
            "SHARED_DIMENSION",
            getModelAnnotations().isSharedDimension());
        rep.saveStepAttribute(
            id_transformation, id_step, "DESCRIPTION", getModelAnnotations().getDescription());

        List<DataProvider> dataProviders = getModelAnnotations().getDataProviders();
        if (dataProviders != null && !dataProviders.isEmpty()) {
          for (int dIdx = 0; dIdx < dataProviders.size(); dIdx++) {

            DataProvider dataProvider = dataProviders.get(dIdx);

            // Save Data Provider properties
            rep.saveStepAttribute(
                id_transformation, id_step, dIdx, "DP_NAME", dataProvider.getName());
            rep.saveStepAttribute(
                id_transformation, id_step, dIdx, "DP_SCHEMA_NAME", dataProvider.getSchemaName());
            rep.saveStepAttribute(
                id_transformation, id_step, dIdx, "DP_TABLE_NAME", dataProvider.getTableName());
            rep.saveStepAttribute(
                id_transformation,
                id_step,
                dIdx,
                "DP_DATABASE_META_NAME_REF",
                dataProvider.getDatabaseMetaNameRef());

            List<ColumnMapping> columnMappings = dataProvider.getColumnMappings();
            if (columnMappings != null && !columnMappings.isEmpty()) {

              // Save count for loading
              rep.saveStepAttribute(
                  id_transformation, id_step, "CM_COUNT_" + dIdx, columnMappings.size());

              for (int cIdx = 0; cIdx < columnMappings.size(); cIdx++) {

                ColumnMapping columnMapping = columnMappings.get(cIdx);

                // Save ColumnMapping properties
                rep.saveStepAttribute(
                    id_transformation, id_step, dIdx, "CM_NAME_" + cIdx, columnMapping.getName());
                rep.saveStepAttribute(
                    id_transformation,
                    id_step,
                    dIdx,
                    "CM_COLUMN_NAME_" + cIdx,
                    columnMapping.getColumnName());

                if (columnMapping.getColumnDataType() != null) {
                  rep.saveStepAttribute(
                      id_transformation,
                      id_step,
                      dIdx,
                      "CM_DATA_TYPE_" + cIdx,
                      columnMapping.getColumnDataType().name());
                }
              }
            }
          }
        }
      }

    } catch (Exception e) {
      throw new KettleException(
          BaseMessages.getString(
                  PKG, "ModelAnnotationMeta.Exception.UnableToSaveStepInfoToRepository")
              + id_step,
          e);
    }
  }