This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 2dcc68b4c6 issue #3025: unique metadata injection keys for Simple 
Mapping (#7956)
2dcc68b4c6 is described below

commit 2dcc68b4c6a79d0c34c8ed0c9a4e95789d46624f
Author: Matt Casters <[email protected]>
AuthorDate: Sat Aug 15 10:18:19 2026 +0200

    issue #3025: unique metadata injection keys for Simple Mapping (#7956)
    
    Add an opt-in injectionKeyPrefix on HopMetadataProperty so reused nested
    types get distinct MDI keys without changing XML/JSON serialization.
    Simple Mapping now prefixes input, output, and parameter mappings, with
    unit tests and MDI integration test 0061.
---
 .../hop/metadata/api/HopMetadataProperty.java      |  10 +
 .../hop/metadata/inject/HopMetadataInjector.java   | 108 +++-
 .../metadata/inject/HopMetadataInjectorTest.java   |  46 ++
 .../hop/metadata/inject/prefix/TwinChild.java      |  34 ++
 .../hop/metadata/inject/prefix/TwinItem.java       |  29 +
 .../hop/metadata/inject/prefix/TwinParent.java     |  32 +
 .../pages/pipeline/transforms/simple-mapping.adoc  |   9 +
 .../hop/core/injection/bean/BeanInjectionInfo.java |  60 +-
 .../hop/core/injection/MetaPropInjectionTest.java  |  41 ++
 .../core/injection/metadata/PrefixedTwinChild.java |  34 ++
 .../core/injection/metadata/PrefixedTwinItem.java  |  29 +
 .../injection/metadata/PrefixedTwinParent.java     |  32 +
 .../mdi/0061-simple-mapping-template.hpl           |  81 +++
 .../mdi/0061-simple-mapping-validate.hpl           | 574 ++++++++++++++++++
 integration-tests/mdi/0061-simple-mapping.hpl      | 668 +++++++++++++++++++++
 .../golden-simple-mapping-input-fields.csv         |   3 +
 .../golden-simple-mapping-output-fields.csv        |   2 +
 .../datasets/golden-simple-mapping-parameters.csv  |   3 +
 .../mdi/datasets/golden-simple-mapping.csv         |   2 +
 integration-tests/mdi/files/simple-mapping.xml     |  66 ++
 integration-tests/mdi/main-0061-simple-mapping.hwf | 113 ++++
 .../golden-simple-mapping-input-fields.json        |  24 +
 .../golden-simple-mapping-output-fields.json       |  24 +
 .../dataset/golden-simple-mapping-parameters.json  |  24 +
 .../metadata/dataset/golden-simple-mapping.json    | 112 ++++
 .../0061-simple-mapping-validate UNIT.json         | 127 ++++
 .../pipeline/transforms/mapping/IOMappings.java    |   6 +-
 .../transforms/mapping/MappingIODefinition.java    |  30 +-
 .../transforms/mapping/MappingParameters.java      |  10 +-
 .../transforms/mapping/MappingValueRename.java     |  10 +-
 .../transforms/mapping/MappingVariableMapping.java |  10 +-
 .../mapping/messages/messages_en_US.properties     |  14 +-
 .../mapping/SimpleMappingMetaInjectionTest.java    | 141 +++++
 33 files changed, 2462 insertions(+), 46 deletions(-)

diff --git 
a/core/src/main/java/org/apache/hop/metadata/api/HopMetadataProperty.java 
b/core/src/main/java/org/apache/hop/metadata/api/HopMetadataProperty.java
index 6700642ac1..469ccb0524 100644
--- a/core/src/main/java/org/apache/hop/metadata/api/HopMetadataProperty.java
+++ b/core/src/main/java/org/apache/hop/metadata/api/HopMetadataProperty.java
@@ -92,6 +92,16 @@ public @interface HopMetadataProperty {
    */
   String injectionKeyDescription() default "";
 
+  /**
+   * Optional prefix applied to descendant injection keys and injection group 
keys. Use this when
+   * the same nested type is referenced more than once (for example input and 
output mappings) so
+   * that metadata injection keys stay unique. Does not affect serialization 
{@link #key()} / {@link
+   * #groupKey()}. Default empty: current behavior.
+   *
+   * @return the prefix for descendant injection keys, or empty
+   */
+  String injectionKeyPrefix() default "";
+
   /**
    * @return The metadata group key to which this property belongs. Don't 
specify any key if you
    *     want this to be the same as key();
diff --git 
a/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java 
b/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
index 1886834016..88ed856739 100644
--- a/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
+++ b/core/src/main/java/org/apache/hop/metadata/inject/HopMetadataInjector.java
@@ -52,6 +52,26 @@ public class HopMetadataInjector {
     // Hidden constructor
   }
 
+  private static String applyPrefix(String prefix, String key) {
+    if (StringUtils.isEmpty(prefix) || StringUtils.isEmpty(key)) {
+      return key;
+    }
+    return prefix + key;
+  }
+
+  /**
+   * Accumulated prefix for descendants of a field; empty field prefix keeps 
the incoming prefix.
+   */
+  private static String nextPrefix(String prefix, String fieldPrefix) {
+    if (StringUtils.isEmpty(fieldPrefix)) {
+      return prefix;
+    }
+    if (StringUtils.isEmpty(prefix)) {
+      return fieldPrefix;
+    }
+    return prefix + fieldPrefix;
+  }
+
   /**
    * For the given object we retrieve the mappings between injection groups 
and their keys.
    *
@@ -64,7 +84,7 @@ public class HopMetadataInjector {
       throw new HopException("The class to find injection group key mappings 
for is null");
     }
     Map<String, Set<String>> map = new HashMap<>();
-    findInjectionGroupKeys(objectClass, map);
+    findInjectionGroupKeys(objectClass, map, "");
     return map;
   }
 
@@ -78,6 +98,11 @@ public class HopMetadataInjector {
    */
   public static boolean isTopLevelInjectionKey(Class<?> objectClass, String 
targetKey)
       throws HopException {
+    return isTopLevelInjectionKey(objectClass, targetKey, "");
+  }
+
+  private static boolean isTopLevelInjectionKey(
+      Class<?> objectClass, String targetKey, String keyPrefix) throws 
HopException {
     if (objectClass == null || StringUtils.isEmpty(targetKey)) {
       return false;
     }
@@ -97,7 +122,8 @@ public class HopMetadataInjector {
         continue;
       }
       String injectionKey =
-          Const.coalesce(property.injectionKey(), property.key(), 
field.getName());
+          applyPrefix(
+              keyPrefix, Const.coalesce(property.injectionKey(), 
property.key(), field.getName()));
       if (targetKey.equals(injectionKey)) {
         return true;
       }
@@ -105,7 +131,8 @@ public class HopMetadataInjector {
           && !fieldClass.isPrimitive()
           && !fieldClass.equals(String.class)
           && !fieldClass.isArray()) {
-        if (isTopLevelInjectionKey(fieldClass, targetKey)) {
+        if (isTopLevelInjectionKey(
+            fieldClass, targetKey, nextPrefix(keyPrefix, 
property.injectionKeyPrefix()))) {
           return true;
         }
       }
@@ -122,6 +149,11 @@ public class HopMetadataInjector {
    */
   public static void findInjectionGroupKeys(Class<?> objectClass, Map<String, 
Set<String>> map)
       throws HopException {
+    findInjectionGroupKeys(objectClass, map, "");
+  }
+
+  private static void findInjectionGroupKeys(
+      Class<?> objectClass, Map<String, Set<String>> map, String keyPrefix) 
throws HopException {
     try {
       List<Field> fields =
           ReflectionUtil.findAllFields(objectClass, new 
MetadataPropertyKeyFunction());
@@ -131,7 +163,9 @@ public class HopMetadataInjector {
         if (property != null && !property.isExcludedFromInjection()) {
           // Does this have an injection group key?
           //
-          String injectionGroup = Const.NVL(property.injectionGroupKey(), 
property.groupKey());
+          String injectionGroup =
+              applyPrefix(keyPrefix, Const.NVL(property.injectionGroupKey(), 
property.groupKey()));
+          String childPrefix = nextPrefix(keyPrefix, 
property.injectionKeyPrefix());
           if (StringUtils.isNotEmpty(injectionGroup)) {
             if (fieldClass.equals(List.class)) {
               // Search in the generic type of the List
@@ -140,16 +174,20 @@ public class HopMetadataInjector {
               if (listItemClass.equals(String.class)) {
                 // We simply map the single injection key with the group key
                 //
-                map.put(injectionGroup, new 
HashSet<>(Set.of(property.injectionKey())));
+                String listKey =
+                    applyPrefix(
+                        childPrefix,
+                        Const.coalesce(property.injectionKey(), 
property.key(), field.getName()));
+                map.put(injectionGroup, new HashSet<>(Set.of(listKey)));
               } else {
-                findInjectionGroupKeys(listItemClass, injectionGroup, map);
+                findInjectionGroupKeys(listItemClass, injectionGroup, map, 
childPrefix);
               }
             }
           } else if (StringUtils.isEmpty(property.injectionGroupKey())) {
             // No injection key or group: we want to look further down.
             // If the class is no primitive, we look into it.
             //
-            findInjectionGroupKeys(fieldClass, map);
+            findInjectionGroupKeys(fieldClass, map, childPrefix);
           }
         }
       }
@@ -166,14 +204,16 @@ public class HopMetadataInjector {
    * @param map The map to add the keys to
    */
   private static void findInjectionGroupKeys(
-      Class<?> fieldClass, String injectionGroup, Map<String, Set<String>> 
map) {
+      Class<?> fieldClass, String injectionGroup, Map<String, Set<String>> 
map, String keyPrefix) {
     List<Field> fields =
         ReflectionUtil.findAllFields(fieldClass, new 
MetadataPropertyKeyFunction());
     for (Field field : fields) {
       HopMetadataProperty property = 
field.getAnnotation(HopMetadataProperty.class);
       if (property != null) {
         String injectionKey =
-            Const.coalesce(property.injectionKey(), property.key(), 
field.getName());
+            applyPrefix(
+                keyPrefix,
+                Const.coalesce(property.injectionKey(), property.key(), 
field.getName()));
         if (StringUtils.isNotEmpty(injectionKey)) {
           // Add to the set for this injection group
           Set<String> keySet = map.computeIfAbsent(injectionGroup, k -> new 
HashSet<>());
@@ -182,7 +222,11 @@ public class HopMetadataInjector {
           // Perhaps this is a subclass and the properties are located deeper.
           //
           Class<?> childClass = field.getType();
-          findInjectionGroupKeys(childClass, injectionGroup, map);
+          findInjectionGroupKeys(
+              childClass,
+              injectionGroup,
+              map,
+              nextPrefix(keyPrefix, property.injectionKeyPrefix()));
         }
       }
     }
@@ -208,6 +252,16 @@ public class HopMetadataInjector {
     // For these fields we see if we can find injection instructions.
     // If this is the case, we inject the metadata.
     //
+    inject(metadataProvider, object, injectionKeyMap, groupKeyMap, "");
+  }
+
+  private static void inject(
+      IHopMetadataProvider metadataProvider,
+      Object object,
+      Map<String, Object> injectionKeyMap,
+      Map<String, RowBuffer> groupKeyMap,
+      String keyPrefix)
+      throws HopException {
     if (object == null) {
       return;
     }
@@ -216,7 +270,8 @@ public class HopMetadataInjector {
       List<Field> fields =
           ReflectionUtil.findAllFields(objectClass, new 
MetadataPropertyKeyFunction());
       for (Field field : fields) {
-        injectField(metadataProvider, object, injectionKeyMap, groupKeyMap, 
field, objectClass);
+        injectField(
+            metadataProvider, object, injectionKeyMap, groupKeyMap, field, 
objectClass, keyPrefix);
       }
     } catch (Exception e) {
       throw new HopException("Error injecting source data into object", e);
@@ -229,7 +284,8 @@ public class HopMetadataInjector {
       Map<String, Object> injectionKeyMap,
       Map<String, RowBuffer> groupKeyMap,
       Field field,
-      Class<?> objectClass)
+      Class<?> objectClass,
+      String keyPrefix)
       throws Exception {
     HopMetadataProperty property = 
field.getAnnotation(HopMetadataProperty.class);
     if (property != null && !property.isExcludedFromInjection()) {
@@ -239,8 +295,11 @@ public class HopMetadataInjector {
       // This is some legacy or misunderstood functionality.
       // The key is the same as the field if none is given.
       String injectionKey =
-          Const.coalesce(property.injectionKey(), property.key(), 
field.getName());
-      String groupKey = Const.coalesce(property.injectionGroupKey(), 
property.groupKey());
+          applyPrefix(
+              keyPrefix, Const.coalesce(property.injectionKey(), 
property.key(), field.getName()));
+      String groupKey =
+          applyPrefix(keyPrefix, Const.coalesce(property.injectionGroupKey(), 
property.groupKey()));
+      String childPrefix = nextPrefix(keyPrefix, 
property.injectionKeyPrefix());
 
       boolean hasKey = StringUtils.isNotEmpty(injectionKey);
       boolean hasKeyData = hasKey && injectionKeyMap.containsKey(injectionKey);
@@ -253,7 +312,8 @@ public class HopMetadataInjector {
       if (hasKey && hasKeyData && !hasGroupData) {
         injectValue(property, metadataProvider, object, field, valueToSet);
       } else if (hasGroupData) {
-        injectList(metadataProvider, object, groupKeyMap, field, objectClass, 
groupKey);
+        injectList(
+            metadataProvider, object, groupKeyMap, field, objectClass, 
groupKey, childPrefix);
       } else if (!fieldClass.isEnum()
           && !fieldClass.isPrimitive()
           && !fieldClass.equals(String.class)
@@ -266,7 +326,13 @@ public class HopMetadataInjector {
         // or enums at this point.  The developer chose not to want this.
         //
         injectIntoInlineField(
-            metadataProvider, object, injectionKeyMap, groupKeyMap, field, 
objectClass);
+            metadataProvider,
+            object,
+            injectionKeyMap,
+            groupKeyMap,
+            field,
+            objectClass,
+            childPrefix);
       }
     }
   }
@@ -277,7 +343,8 @@ public class HopMetadataInjector {
       Map<String, Object> dataMap,
       Map<String, RowBuffer> listItemData,
       Field field,
-      Class<?> objectClass)
+      Class<?> objectClass,
+      String keyPrefix)
       throws IllegalAccessException,
           InvocationTargetException,
           NoSuchMethodException,
@@ -293,7 +360,7 @@ public class HopMetadataInjector {
               + objectClass.getName()
               + " has a value to inject into");
     }
-    inject(metadataProvider, fieldObject, dataMap, listItemData);
+    inject(metadataProvider, fieldObject, dataMap, listItemData, keyPrefix);
   }
 
   private static void injectValue(
@@ -516,7 +583,8 @@ public class HopMetadataInjector {
       Map<String, RowBuffer> listItemData,
       Field field,
       Class<?> objectClass,
-      String groupKey)
+      String groupKey,
+      String itemKeyPrefix)
       throws HopException {
     try {
       // What is the list?
@@ -575,7 +643,7 @@ public class HopMetadataInjector {
             } else {
               Map<String, Object> listItemValueMap = new HashMap<>();
               listItemValueMap.put(valueKey, value);
-              inject(metadataProvider, listItemObject, listItemValueMap, 
Map.of());
+              inject(metadataProvider, listItemObject, listItemValueMap, 
Map.of(), itemKeyPrefix);
             }
           }
         }
diff --git 
a/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
 
b/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
index 6cc480bede..194c4e2a83 100644
--- 
a/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
+++ 
b/core/src/test/java/org/apache/hop/metadata/inject/HopMetadataInjectorTest.java
@@ -47,6 +47,7 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.IEnumHasCodeAndDescription;
 import org.apache.hop.metadata.inject.company.Company;
 import org.apache.hop.metadata.inject.company.Employee;
+import org.apache.hop.metadata.inject.prefix.TwinParent;
 import org.apache.hop.metadata.inject.sample.Field;
 import org.apache.hop.metadata.inject.sample.SampleMeta;
 import org.apache.hop.metadata.inject.sample.SampleType;
@@ -296,6 +297,51 @@ class HopMetadataInjectorTest {
     assertTrue(moreFields.contains("FIELD_TRIM_TYPE"));
   }
 
+  @Test
+  void injectionKeyPrefixDisambiguatesReusedNestedType() throws Exception {
+    TwinParent parent = new TwinParent();
+
+    Map<String, Object> injectionKeyMap = new HashMap<>();
+    injectionKeyMap.put("LEFT_NAME", "west");
+    injectionKeyMap.put("RIGHT_NAME", "east");
+
+    Map<String, RowBuffer> injectionGroupMap = new HashMap<>();
+    RowBuffer leftItems = new RowBuffer();
+    leftItems.setRowMeta(new RowMetaBuilder().addString("LEFT_VALUE").build());
+    leftItems.addRow("l1");
+    leftItems.addRow("l2");
+    injectionGroupMap.put("LEFT_ITEMS", leftItems);
+
+    RowBuffer rightItems = new RowBuffer();
+    rightItems.setRowMeta(new 
RowMetaBuilder().addString("RIGHT_VALUE").build());
+    rightItems.addRow("r1");
+    injectionGroupMap.put("RIGHT_ITEMS", rightItems);
+
+    HopMetadataInjector.inject(
+        new MemoryMetadataProvider(), parent, injectionKeyMap, 
injectionGroupMap);
+
+    assertEquals("west", parent.getLeft().getName());
+    assertEquals("east", parent.getRight().getName());
+    assertEquals(2, parent.getLeft().getItems().size());
+    assertEquals("l1", parent.getLeft().getItems().get(0).getValue());
+    assertEquals("l2", parent.getLeft().getItems().get(1).getValue());
+    assertEquals(1, parent.getRight().getItems().size());
+    assertEquals("r1", parent.getRight().getItems().get(0).getValue());
+  }
+
+  @Test
+  void injectionKeyPrefixGroupKeysAreUnique() throws Exception {
+    Map<String, Set<String>> map = 
HopMetadataInjector.findInjectionGroupKeys(TwinParent.class);
+    assertEquals(2, map.size());
+    assertEquals(Set.of("LEFT_VALUE"), map.get("LEFT_ITEMS"));
+    assertEquals(Set.of("RIGHT_VALUE"), map.get("RIGHT_ITEMS"));
+
+    assertTrue(HopMetadataInjector.isTopLevelInjectionKey(TwinParent.class, 
"LEFT_NAME"));
+    assertTrue(HopMetadataInjector.isTopLevelInjectionKey(TwinParent.class, 
"RIGHT_NAME"));
+    assertFalse(HopMetadataInjector.isTopLevelInjectionKey(TwinParent.class, 
"LEFT_VALUE"));
+    assertFalse(HopMetadataInjector.isTopLevelInjectionKey(TwinParent.class, 
"NAME"));
+  }
+
   @Test
   void testStoreWithCodeEnumInjectionWithDescription() throws Exception {
     Map<String, Object> injectionKeyMap = new HashMap<>();
diff --git 
a/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinChild.java 
b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinChild.java
new file mode 100644
index 0000000000..e482ac8f89
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinChild.java
@@ -0,0 +1,34 @@
+/*
+ * 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.hop.metadata.inject.prefix;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class TwinChild {
+  @HopMetadataProperty(key = "name", injectionKey = "NAME")
+  private String name;
+
+  @HopMetadataProperty(key = "item", injectionGroupKey = "ITEMS")
+  private List<TwinItem> items = new ArrayList<>();
+}
diff --git 
a/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinItem.java 
b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinItem.java
new file mode 100644
index 0000000000..6701b55068
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinItem.java
@@ -0,0 +1,29 @@
+/*
+ * 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.hop.metadata.inject.prefix;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class TwinItem {
+  @HopMetadataProperty(key = "value", injectionKey = "VALUE")
+  private String value;
+}
diff --git 
a/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinParent.java 
b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinParent.java
new file mode 100644
index 0000000000..e8a2102fc9
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/metadata/inject/prefix/TwinParent.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hop.metadata.inject.prefix;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class TwinParent {
+  @HopMetadataProperty(key = "left", injectionKeyPrefix = "LEFT_")
+  private TwinChild left = new TwinChild();
+
+  @HopMetadataProperty(key = "right", injectionKeyPrefix = "RIGHT_")
+  private TwinChild right = new TwinChild();
+}
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/simple-mapping.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/simple-mapping.adoc
index 9fa11d91f4..6d6b6ff4f5 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/simple-mapping.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/simple-mapping.adoc
@@ -57,6 +57,15 @@ It is always executed with a standard local Hop pipeline run 
configuration since
 You can map (hence the name) field names.
 * Output tab: specify how to rename output fields retrieved from the 
xref:pipeline/transforms/mapping-output.adoc[Mapping output] transform.
 
+== Metadata Injection
+
+Input and output mappings reuse the same nested fields, so injection keys are 
prefixed to keep them unique.
+
+* Top-level: `filename`, `runConfiguration`, `INPUT_SOURCE_TRANSFORM`, 
`INPUT_TARGET_TRANSFORM`, `INPUT_DESCRIPTION`, `INPUT_MAIN_PATH`, 
`INPUT_RENAME_ON_OUTPUT`, and the matching `OUTPUT_*` keys, plus 
`PARAMETERS_INHERIT_ALL_VARIABLES`
+* Group `INPUT_FIELDS`: `INPUT_SOURCE_FIELD`, `INPUT_TARGET_FIELD`
+* Group `OUTPUT_FIELDS`: `OUTPUT_SOURCE_FIELD`, `OUTPUT_TARGET_FIELD`
+* Group `PARAMETERS_MAPPINGS`: `PARAMETERS_VARIABLE`, `PARAMETERS_VALUE`
+
 == Sample
 
 In the samples project you can find two examples of the Simple Mapping 
transform, the transforms/files/simple-mapping-child.hpl and 
simple-mapping-parent.hpl pipelines.
diff --git 
a/engine/src/main/java/org/apache/hop/core/injection/bean/BeanInjectionInfo.java
 
b/engine/src/main/java/org/apache/hop/core/injection/bean/BeanInjectionInfo.java
index b75628223a..af7040922b 100644
--- 
a/engine/src/main/java/org/apache/hop/core/injection/bean/BeanInjectionInfo.java
+++ 
b/engine/src/main/java/org/apache/hop/core/injection/bean/BeanInjectionInfo.java
@@ -117,7 +117,7 @@ public class BeanInjectionInfo<Meta extends Object> {
 
     List<BeanLevelInfo> parentPath = Arrays.asList(classLevelInfo);
 
-    boolean hasChildren = extractMetadataProperties(rootGroup, parentPath, 
clazz, null);
+    boolean hasChildren = extractMetadataProperties(rootGroup, parentPath, 
clazz, null, "");
     if (!hasChildren) {
       throw new HopRuntimeException("Injection not supported in " + clazz);
     }
@@ -130,7 +130,8 @@ public class BeanInjectionInfo<Meta extends Object> {
       Group rootGroup,
       List<BeanLevelInfo> parentPath,
       Class<?> clazz,
-      HopMetadataProperty parentProperty) {
+      HopMetadataProperty parentProperty,
+      String keyPrefix) {
 
     Set<String> childKeysToIgnore = getChildKeysToIgnore(parentProperty);
 
@@ -153,10 +154,11 @@ public class BeanInjectionInfo<Meta extends Object> {
 
       HopMetadataProperty property = propertyFields.get(field);
 
-      String injectionKey = calculateInjectionKey(field, property);
+      String injectionKey = applyPrefix(keyPrefix, 
calculateInjectionKey(field, property));
       String injectionKeyDescription = 
calculateInjectionKeyDescription(property);
-      String injectionGroupKey = calculateInjectionGroupKey(property);
+      String injectionGroupKey = applyPrefix(keyPrefix, 
calculateInjectionGroupKey(property));
       String injectionGroupDescription = 
calculateInjectionGroupDescription(property);
+      String childPrefix = nextPrefix(keyPrefix, 
property.injectionKeyPrefix());
 
       // Class Bean Level Info...
       //
@@ -219,14 +221,15 @@ public class BeanInjectionInfo<Meta extends Object> {
                   path,
                   property.isExcludedFromInjection());
           group.properties.add(p);
-          properties.put(injectionKey, p);
+          registerProperty(injectionKey, p);
         } else {
           Set<String> ignoreKeys = new 
HashSet<>(Set.of(property.childKeysToIgnore()));
           for (Field childField : ReflectionUtil.findAllFields(fieldType)) {
             Class<?> childFieldType = childField.getType();
             HopMetadataProperty childProperty = 
childField.getAnnotation(HopMetadataProperty.class);
             if (childProperty != null && 
!ignoreKeys.contains(childProperty.key())) {
-              String childInjectionKey = calculateInjectionKey(childField, 
childProperty);
+              String childInjectionKey =
+                  applyPrefix(childPrefix, calculateInjectionKey(childField, 
childProperty));
               String childInjectionKeyDescription = 
calculateInjectionKeyDescription(childProperty);
 
               // Child bean level info...
@@ -253,11 +256,16 @@ public class BeanInjectionInfo<Meta extends Object> {
                         path,
                         property.isExcludedFromInjection());
                 group.properties.add(p);
-                properties.put(childInjectionKey, p);
+                registerProperty(childInjectionKey, p);
               } else {
                 // Extract properties of this child as well...
                 //
-                extractMetadataProperties(rootGroup, path, childFieldType, 
childProperty);
+                extractMetadataProperties(
+                    rootGroup,
+                    path,
+                    childFieldType,
+                    childProperty,
+                    nextPrefix(childPrefix, 
childProperty.injectionKeyPrefix()));
               }
             }
           }
@@ -282,20 +290,52 @@ public class BeanInjectionInfo<Meta extends Object> {
                   path,
                   property.isExcludedFromInjection());
           rootGroup.properties.add(p);
-          properties.put(injectionKey, p);
+          registerProperty(injectionKey, p);
         }
         if (!childless) {
           // POJO: look deeper..
           //
           List<BeanLevelInfo> path = new ArrayList<>(parentPath);
           path.add(fieldLevelInfo);
-          extractMetadataProperties(rootGroup, path, fieldType, property);
+          extractMetadataProperties(rootGroup, path, fieldType, property, 
childPrefix);
         }
       }
     }
     return true;
   }
 
+  private void registerProperty(String injectionKey, Property property) {
+    if (properties.containsKey(injectionKey)) {
+      log.logBasic(
+          "Duplicate metadata injection key '"
+              + injectionKey
+              + "' in "
+              + clazz.getName()
+              + "; last definition wins");
+    }
+    properties.put(injectionKey, property);
+  }
+
+  private static String applyPrefix(String prefix, String key) {
+    if (StringUtils.isEmpty(prefix) || StringUtils.isEmpty(key)) {
+      return key;
+    }
+    return prefix + key;
+  }
+
+  /**
+   * Accumulated prefix for descendants of a field; empty field prefix keeps 
the incoming prefix.
+   */
+  private static String nextPrefix(String prefix, String fieldPrefix) {
+    if (StringUtils.isEmpty(fieldPrefix)) {
+      return prefix;
+    }
+    if (StringUtils.isEmpty(prefix)) {
+      return fieldPrefix;
+    }
+    return prefix + fieldPrefix;
+  }
+
   private static @NonNull Set<String> getChildKeysToIgnore(HopMetadataProperty 
property) {
     Set<String> childKeysToIgnore = new HashSet<>();
     if (property != null) {
diff --git 
a/engine/src/test/java/org/apache/hop/core/injection/MetaPropInjectionTest.java 
b/engine/src/test/java/org/apache/hop/core/injection/MetaPropInjectionTest.java
index 9feeb56d9a..4aee022374 100644
--- 
a/engine/src/test/java/org/apache/hop/core/injection/MetaPropInjectionTest.java
+++ 
b/engine/src/test/java/org/apache/hop/core/injection/MetaPropInjectionTest.java
@@ -28,6 +28,7 @@ import org.apache.hop.core.RowMetaAndData;
 import org.apache.hop.core.injection.bean.BeanInjectionInfo;
 import org.apache.hop.core.injection.bean.BeanInjector;
 import org.apache.hop.core.injection.bean.BeanLevelInfo;
+import org.apache.hop.core.injection.metadata.PrefixedTwinParent;
 import org.apache.hop.core.injection.metadata.PropBeanChild;
 import org.apache.hop.core.injection.metadata.PropBeanGrandChild;
 import org.apache.hop.core.injection.metadata.PropBeanListChild;
@@ -237,4 +238,44 @@ class MetaPropInjectionTest {
     assertEquals(new PropBeanListChild("f1_2", "f2_2"), 
parent.getChildren().get(1));
     assertEquals(new PropBeanListChild("f1_3", "f2_3"), 
parent.getChildren().get(2));
   }
+
+  @Test
+  void injectionKeyPrefixCreatesUniqueKeysAndInjectsIndependently() throws 
Exception {
+    BeanInjectionInfo<PrefixedTwinParent> info = new 
BeanInjectionInfo<>(PrefixedTwinParent.class);
+
+    assertNotNull(info.getProperties().get("LEFT_NAME"));
+    assertNotNull(info.getProperties().get("RIGHT_NAME"));
+    assertNotNull(info.getProperties().get("LEFT_VALUE"));
+    assertNotNull(info.getProperties().get("RIGHT_VALUE"));
+    assertNull(info.getProperties().get("NAME"));
+    assertEquals("LEFT_ITEMS", 
info.getProperties().get("LEFT_VALUE").getGroupKey());
+    assertEquals("RIGHT_ITEMS", 
info.getProperties().get("RIGHT_VALUE").getGroupKey());
+
+    PrefixedTwinParent parent = new PrefixedTwinParent();
+    BeanInjector<PrefixedTwinParent> injector =
+        new BeanInjector<>(info, new MemoryMetadataProvider());
+
+    IRowMeta nameMeta = new RowMetaBuilder().addString("name").build();
+    injector.setProperty(
+        parent, "LEFT_NAME", List.of(new RowMetaAndData(nameMeta, "west")), 
"name");
+    injector.setProperty(
+        parent, "RIGHT_NAME", List.of(new RowMetaAndData(nameMeta, "east")), 
"name");
+
+    IRowMeta valueMeta = new RowMetaBuilder().addString("value").build();
+    injector.setProperty(
+        parent,
+        "LEFT_VALUE",
+        List.of(new RowMetaAndData(valueMeta, "l1"), new 
RowMetaAndData(valueMeta, "l2")),
+        "value");
+    injector.setProperty(
+        parent, "RIGHT_VALUE", List.of(new RowMetaAndData(valueMeta, "r1")), 
"value");
+
+    assertEquals("west", parent.getLeft().getName());
+    assertEquals("east", parent.getRight().getName());
+    assertEquals(2, parent.getLeft().getItems().size());
+    assertEquals("l1", parent.getLeft().getItems().get(0).getValue());
+    assertEquals("l2", parent.getLeft().getItems().get(1).getValue());
+    assertEquals(1, parent.getRight().getItems().size());
+    assertEquals("r1", parent.getRight().getItems().get(0).getValue());
+  }
 }
diff --git 
a/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinChild.java
 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinChild.java
new file mode 100644
index 0000000000..573c06594d
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinChild.java
@@ -0,0 +1,34 @@
+/*
+ * 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.hop.core.injection.metadata;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class PrefixedTwinChild {
+  @HopMetadataProperty(key = "name", injectionKey = "NAME")
+  private String name;
+
+  @HopMetadataProperty(key = "item", injectionGroupKey = "ITEMS")
+  private List<PrefixedTwinItem> items = new ArrayList<>();
+}
diff --git 
a/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinItem.java
 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinItem.java
new file mode 100644
index 0000000000..acc5df503b
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinItem.java
@@ -0,0 +1,29 @@
+/*
+ * 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.hop.core.injection.metadata;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class PrefixedTwinItem {
+  @HopMetadataProperty(key = "value", injectionKey = "VALUE")
+  private String value;
+}
diff --git 
a/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinParent.java
 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinParent.java
new file mode 100644
index 0000000000..70fbecc671
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/core/injection/metadata/PrefixedTwinParent.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hop.core.injection.metadata;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+
+@Getter
+@Setter
+public class PrefixedTwinParent {
+  @HopMetadataProperty(key = "left", injectionKeyPrefix = "LEFT_")
+  private PrefixedTwinChild left = new PrefixedTwinChild();
+
+  @HopMetadataProperty(key = "right", injectionKeyPrefix = "RIGHT_")
+  private PrefixedTwinChild right = new PrefixedTwinChild();
+}
diff --git a/integration-tests/mdi/0061-simple-mapping-template.hpl 
b/integration-tests/mdi/0061-simple-mapping-template.hpl
new file mode 100644
index 0000000000..08653f0e27
--- /dev/null
+++ b/integration-tests/mdi/0061-simple-mapping-template.hpl
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0061-simple-mapping-template</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/08/14 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/08/14 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+  </order>
+  <transform>
+    <name>Simple mapping</name>
+    <type>SimpleMapping</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <filename/>
+    <runConfiguration/>
+    <mappings>
+      <input>
+        <mapping>
+          <main_path>N</main_path>
+          <rename_on_output>N</rename_on_output>
+        </mapping>
+      </input>
+      <output>
+        <mapping>
+          <main_path>N</main_path>
+          <rename_on_output>N</rename_on_output>
+        </mapping>
+      </output>
+      <parameters>
+        <inherit_all_vars>Y</inherit_all_vars>
+      </parameters>
+    </mappings>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0061-simple-mapping-validate.hpl 
b/integration-tests/mdi/0061-simple-mapping-validate.hpl
new file mode 100644
index 0000000000..1acbc7f9ef
--- /dev/null
+++ b/integration-tests/mdi/0061-simple-mapping-validate.hpl
@@ -0,0 +1,574 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0061-simple-mapping-validate</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/08/14 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/08/14 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>injected pipeline</from>
+      <to>validate</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>injected input fields</from>
+      <to>validate input fields</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>injected output fields</from>
+      <to>validate output fields</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>injected parameters</from>
+      <to>validate parameters</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>injected pipeline</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>filename</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>filename</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>runConfiguration</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>runConfiguration</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_source</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/input_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_target</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/output_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/description</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_main_path</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/main_path</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_rename_on_output</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/rename_on_output</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_source</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/input_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_target</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/output_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/description</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_main_path</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/main_path</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_rename_on_output</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/rename_on_output</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>inherit_all_vars</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/parameters/inherit_all_vars</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/output/0061-simple-mapping-injected.hpl</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/pipeline/transform</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>injected input fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>parent</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>parent</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>child</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>child</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/output/0061-simple-mapping-injected.hpl</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/pipeline/transform/mappings/input/mapping/connector</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>injected output fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>parent</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>parent</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>child</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>child</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/output/0061-simple-mapping-injected.hpl</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    
<loopxpath>/pipeline/transform/mappings/output/mapping/connector</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>272</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>injected parameters</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>variable</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>variable</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>input</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/output/0061-simple-mapping-injected.hpl</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    
<loopxpath>/pipeline/transform/mappings/parameters/variablemapping</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>368</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>validate</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>validate input fields</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>validate output fields</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>272</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>validate parameters</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>368</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0061-simple-mapping.hpl 
b/integration-tests/mdi/0061-simple-mapping.hpl
new file mode 100644
index 0000000000..4fbacebe8d
--- /dev/null
+++ b/integration-tests/mdi/0061-simple-mapping.hpl
@@ -0,0 +1,668 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0061-simple-mapping</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/08/14 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/08/14 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>files/simple-mapping.xml</from>
+      <to>0061-simple-mapping-template.hpl</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>files/simple-mapping.xml input fields</from>
+      <to>0061-simple-mapping-template.hpl</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>files/simple-mapping.xml output fields</from>
+      <to>0061-simple-mapping-template.hpl</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>files/simple-mapping.xml parameters</from>
+      <to>0061-simple-mapping-template.hpl</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>0061-simple-mapping-template.hpl</name>
+    <type>MetaInject</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <filename>${PROJECT_HOME}/0061-simple-mapping-template.hpl</filename>
+    <run_configuration>local</run_configuration>
+    <source_transform/>
+    <source_output_fields>    </source_output_fields>
+    
<target_file>${PROJECT_HOME}/output/0061-simple-mapping-injected.hpl</target_file>
+    <create_parent_folder>Y</create_parent_folder>
+    <no_execution>Y</no_execution>
+    <allow_empty_stream_on_execution>N</allow_empty_stream_on_execution>
+    <stream_source_transform/>
+    <stream_target_transform/>
+    <mappings>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>filename</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>filename</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>runConfiguration</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>runConfiguration</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_SOURCE_TRANSFORM</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>input_source</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_TARGET_TRANSFORM</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>input_target</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_DESCRIPTION</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>input_description</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_MAIN_PATH</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>input_main_path</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_RENAME_ON_OUTPUT</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>input_rename_on_output</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_SOURCE_TRANSFORM</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>output_source</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_TARGET_TRANSFORM</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>output_target</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_DESCRIPTION</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>output_description</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_MAIN_PATH</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>output_main_path</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_RENAME_ON_OUTPUT</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>output_rename_on_output</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        
<target_attribute_key>PARAMETERS_INHERIT_ALL_VARIABLES</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>files/simple-mapping.xml</source_transform>
+        <source_field>inherit_all_vars</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_SOURCE_FIELD</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml input 
fields</source_transform>
+        <source_field>parent</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>INPUT_TARGET_FIELD</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml input 
fields</source_transform>
+        <source_field>child</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_SOURCE_FIELD</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml output 
fields</source_transform>
+        <source_field>parent</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>OUTPUT_TARGET_FIELD</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml output 
fields</source_transform>
+        <source_field>child</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>PARAMETERS_VARIABLE</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml 
parameters</source_transform>
+        <source_field>variable</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Simple mapping</target_transform_name>
+        <target_attribute_key>PARAMETERS_VALUE</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/simple-mapping.xml 
parameters</source_transform>
+        <source_field>input</source_field>
+      </mapping>
+    </mappings>
+    <attributes/>
+    <GUI>
+      <xloc>512</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>files/simple-mapping.xml</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>filename</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>filename</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>runConfiguration</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>runConfiguration</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_source</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/input_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_target</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/output_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/description</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_main_path</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/main_path</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input_rename_on_output</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/input/mapping/rename_on_output</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_source</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/input_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_target</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/output_transform</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/description</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_main_path</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/main_path</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>output_rename_on_output</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/output/mapping/rename_on_output</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>inherit_all_vars</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>mappings/parameters/inherit_all_vars</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>N</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/simple-mapping.xml</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/transform</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>files/simple-mapping.xml input fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>parent</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>parent</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>child</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>child</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>N</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/simple-mapping.xml</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/transform/mappings/input/mapping/connector</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>files/simple-mapping.xml output fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>parent</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>parent</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>child</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>child</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>N</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/simple-mapping.xml</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/transform/mappings/output/mapping/connector</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>272</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>files/simple-mapping.xml parameters</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>variable</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>variable</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>input</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>input</xPath>
+      </field>
+    </fields>
+    <file>
+      <file_required>N</file_required>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/simple-mapping.xml</name>
+    </file>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <limit>0</limit>
+    <loopxpath>/transform/mappings/parameters/variablemapping</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <readurl>N</readurl>
+    <rownum>N</rownum>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>368</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/mdi/datasets/golden-simple-mapping-input-fields.csv 
b/integration-tests/mdi/datasets/golden-simple-mapping-input-fields.csv
new file mode 100644
index 0000000000..9ac288b41a
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-simple-mapping-input-fields.csv
@@ -0,0 +1,3 @@
+parent,child
+firstName,fname
+lastName,lname
diff --git 
a/integration-tests/mdi/datasets/golden-simple-mapping-output-fields.csv 
b/integration-tests/mdi/datasets/golden-simple-mapping-output-fields.csv
new file mode 100644
index 0000000000..d8dca113d0
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-simple-mapping-output-fields.csv
@@ -0,0 +1,2 @@
+parent,child
+fullName,name
diff --git 
a/integration-tests/mdi/datasets/golden-simple-mapping-parameters.csv 
b/integration-tests/mdi/datasets/golden-simple-mapping-parameters.csv
new file mode 100644
index 0000000000..02c4f710ae
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-simple-mapping-parameters.csv
@@ -0,0 +1,3 @@
+variable,input
+LIMIT,100
+ENV,dev
diff --git a/integration-tests/mdi/datasets/golden-simple-mapping.csv 
b/integration-tests/mdi/datasets/golden-simple-mapping.csv
new file mode 100644
index 0000000000..ab10371369
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-simple-mapping.csv
@@ -0,0 +1,2 @@
+filename,runConfiguration,input_source,input_target,input_description,input_main_path,input_rename_on_output,output_source,output_target,output_description,output_main_path,output_rename_on_output,inherit_all_vars
+child-mapping.hpl,local,Reader,Mapping input,Input path,Y,Y,Mapping 
output,Writer,Output path,N,N,N
diff --git a/integration-tests/mdi/files/simple-mapping.xml 
b/integration-tests/mdi/files/simple-mapping.xml
new file mode 100644
index 0000000000..d07b24249f
--- /dev/null
+++ b/integration-tests/mdi/files/simple-mapping.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<transform>
+    <filename>child-mapping.hpl</filename>
+    <runConfiguration>local</runConfiguration>
+    <mappings>
+        <input>
+            <mapping>
+                <input_transform>Reader</input_transform>
+                <output_transform>Mapping input</output_transform>
+                <description>Input path</description>
+                <main_path>Y</main_path>
+                <rename_on_output>Y</rename_on_output>
+                <connector>
+                    <parent>firstName</parent>
+                    <child>fname</child>
+                </connector>
+                <connector>
+                    <parent>lastName</parent>
+                    <child>lname</child>
+                </connector>
+            </mapping>
+        </input>
+        <output>
+            <mapping>
+                <input_transform>Mapping output</input_transform>
+                <output_transform>Writer</output_transform>
+                <description>Output path</description>
+                <main_path>N</main_path>
+                <rename_on_output>N</rename_on_output>
+                <connector>
+                    <parent>fullName</parent>
+                    <child>name</child>
+                </connector>
+            </mapping>
+        </output>
+        <parameters>
+            <inherit_all_vars>N</inherit_all_vars>
+            <variablemapping>
+                <variable>LIMIT</variable>
+                <input>100</input>
+            </variablemapping>
+            <variablemapping>
+                <variable>ENV</variable>
+                <input>dev</input>
+            </variablemapping>
+        </parameters>
+    </mappings>
+</transform>
diff --git a/integration-tests/mdi/main-0061-simple-mapping.hwf 
b/integration-tests/mdi/main-0061-simple-mapping.hwf
new file mode 100644
index 0000000000..43c6d5c261
--- /dev/null
+++ b/integration-tests/mdi/main-0061-simple-mapping.hwf
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>main-0061-simple-mapping</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/08/14 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/08/14 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>112</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0061-simple-mapping</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <clear_files>N</clear_files>
+      <clear_rows>N</clear_rows>
+      <create_parent_folder>N</create_parent_folder>
+      <exec_per_row>N</exec_per_row>
+      <filename>${PROJECT_HOME}/0061-simple-mapping.hpl</filename>
+      <loglevel>Basic</loglevel>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <params_from_previous>N</params_from_previous>
+      <run_configuration>local</run_configuration>
+      <set_append_logfile>N</set_append_logfile>
+      <set_logfile>N</set_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0061-simple-mapping-validate UNIT</name>
+      <description/>
+      <type>RunPipelineTests</type>
+      <attributes/>
+      <test_names>
+        <test_name>
+          <name>0061-simple-mapping-validate UNIT</name>
+        </test_name>
+      </test_names>
+      <parallel>N</parallel>
+      <xloc>512</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>0061-simple-mapping</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>0061-simple-mapping</from>
+      <to>0061-simple-mapping-validate UNIT</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-simple-mapping-input-fields.json
 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-input-fields.json
new file mode 100644
index 0000000000..649aa579fc
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-input-fields.json
@@ -0,0 +1,24 @@
+{
+  "base_filename": "golden-simple-mapping-input-fields.csv",
+  "name": "golden-simple-mapping-input-fields",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "parent",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "child",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-simple-mapping-output-fields.json
 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-output-fields.json
new file mode 100644
index 0000000000..5c321a6c3f
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-output-fields.json
@@ -0,0 +1,24 @@
+{
+  "base_filename": "golden-simple-mapping-output-fields.csv",
+  "name": "golden-simple-mapping-output-fields",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "parent",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "child",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-simple-mapping-parameters.json 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-parameters.json
new file mode 100644
index 0000000000..537aed4f51
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping-parameters.json
@@ -0,0 +1,24 @@
+{
+  "base_filename": "golden-simple-mapping-parameters.csv",
+  "name": "golden-simple-mapping-parameters",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "variable",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
diff --git a/integration-tests/mdi/metadata/dataset/golden-simple-mapping.json 
b/integration-tests/mdi/metadata/dataset/golden-simple-mapping.json
new file mode 100644
index 0000000000..22398d1baa
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-simple-mapping.json
@@ -0,0 +1,112 @@
+{
+  "base_filename": "golden-simple-mapping.csv",
+  "name": "golden-simple-mapping",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "filename",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "runConfiguration",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input_source",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input_target",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input_description",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input_main_path",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "input_rename_on_output",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "output_source",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "output_target",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "output_description",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "output_main_path",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "output_rename_on_output",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "inherit_all_vars",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/mdi/metadata/unit-test/0061-simple-mapping-validate 
UNIT.json 
b/integration-tests/mdi/metadata/unit-test/0061-simple-mapping-validate 
UNIT.json
new file mode 100644
index 0000000000..7d959be863
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0061-simple-mapping-validate 
UNIT.json   
@@ -0,0 +1,127 @@
+{
+  "database_replacements": [],
+  "autoOpening": true,
+  "description": "",
+  "persist_filename": "",
+  "test_type": "UNIT_TEST",
+  "variableValues": [],
+  "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "filename",
+          "data_set_field": "filename"
+        },
+        {
+          "transform_field": "runConfiguration",
+          "data_set_field": "runConfiguration"
+        },
+        {
+          "transform_field": "input_source",
+          "data_set_field": "input_source"
+        },
+        {
+          "transform_field": "input_target",
+          "data_set_field": "input_target"
+        },
+        {
+          "transform_field": "input_description",
+          "data_set_field": "input_description"
+        },
+        {
+          "transform_field": "input_main_path",
+          "data_set_field": "input_main_path"
+        },
+        {
+          "transform_field": "input_rename_on_output",
+          "data_set_field": "input_rename_on_output"
+        },
+        {
+          "transform_field": "output_source",
+          "data_set_field": "output_source"
+        },
+        {
+          "transform_field": "output_target",
+          "data_set_field": "output_target"
+        },
+        {
+          "transform_field": "output_description",
+          "data_set_field": "output_description"
+        },
+        {
+          "transform_field": "output_main_path",
+          "data_set_field": "output_main_path"
+        },
+        {
+          "transform_field": "output_rename_on_output",
+          "data_set_field": "output_rename_on_output"
+        },
+        {
+          "transform_field": "inherit_all_vars",
+          "data_set_field": "inherit_all_vars"
+        }
+      ],
+      "field_order": [
+        "filename"
+      ],
+      "data_set_name": "golden-simple-mapping",
+      "transform_name": "validate"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "parent",
+          "data_set_field": "parent"
+        },
+        {
+          "transform_field": "child",
+          "data_set_field": "child"
+        }
+      ],
+      "field_order": [
+        "parent"
+      ],
+      "data_set_name": "golden-simple-mapping-input-fields",
+      "transform_name": "validate input fields"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "parent",
+          "data_set_field": "parent"
+        },
+        {
+          "transform_field": "child",
+          "data_set_field": "child"
+        }
+      ],
+      "field_order": [
+        "parent"
+      ],
+      "data_set_name": "golden-simple-mapping-output-fields",
+      "transform_name": "validate output fields"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "variable",
+          "data_set_field": "variable"
+        },
+        {
+          "transform_field": "input",
+          "data_set_field": "input"
+        }
+      ],
+      "field_order": [
+        "variable"
+      ],
+      "data_set_name": "golden-simple-mapping-parameters",
+      "transform_name": "validate parameters"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0061-simple-mapping-validate UNIT",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0061-simple-mapping-validate.hpl"
+}
diff --git 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/IOMappings.java
 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/IOMappings.java
index 23aa82a769..8bef9f415c 100644
--- 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/IOMappings.java
+++ 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/IOMappings.java
@@ -21,13 +21,13 @@ package org.apache.hop.pipeline.transforms.mapping;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 
 public class IOMappings implements Cloneable {
-  @HopMetadataProperty(key = "input")
+  @HopMetadataProperty(key = "input", injectionKeyPrefix = "INPUT_")
   private MappingIODefinition inputMapping;
 
-  @HopMetadataProperty(key = "output")
+  @HopMetadataProperty(key = "output", injectionKeyPrefix = "OUTPUT_")
   private MappingIODefinition outputMapping;
 
-  @HopMetadataProperty(key = "parameters")
+  @HopMetadataProperty(key = "parameters", injectionKeyPrefix = "PARAMETERS_")
   private MappingParameters mappingParameters;
 
   public IOMappings() {
diff --git 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingIODefinition.java
 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingIODefinition.java
index 954d52eec6..7b8e02869b 100644
--- 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingIODefinition.java
+++ 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingIODefinition.java
@@ -37,22 +37,40 @@ public class MappingIODefinition implements Cloneable {
   @SuppressWarnings("java:S2065")
   private transient TransformMeta inputTransform;
 
-  @HopMetadataProperty(key = "input_transform")
+  @HopMetadataProperty(
+      key = "input_transform",
+      injectionKey = "SOURCE_TRANSFORM",
+      injectionKeyDescription = "SimpleMapping.Injection.SOURCE_TRANSFORM")
   private String inputTransformName;
 
-  @HopMetadataProperty(key = "output_transform")
+  @HopMetadataProperty(
+      key = "output_transform",
+      injectionKey = "TARGET_TRANSFORM",
+      injectionKeyDescription = "SimpleMapping.Injection.TARGET_TRANSFORM")
   private String outputTransformName;
 
-  @HopMetadataProperty(key = "description")
+  @HopMetadataProperty(
+      key = "description",
+      injectionKey = "DESCRIPTION",
+      injectionKeyDescription = "SimpleMapping.Injection.DESCRIPTION")
   private String description;
 
-  @HopMetadataProperty(key = "connector")
+  @HopMetadataProperty(
+      key = "connector",
+      injectionGroupKey = "FIELDS",
+      injectionGroupDescription = "SimpleMapping.Injection.Group.FIELDS")
   private List<MappingValueRename> valueRenames;
 
-  @HopMetadataProperty(key = "main_path")
+  @HopMetadataProperty(
+      key = "main_path",
+      injectionKey = "MAIN_PATH",
+      injectionKeyDescription = "SimpleMapping.Injection.MAIN_PATH")
   private boolean mainDataPath;
 
-  @HopMetadataProperty(key = "rename_on_output")
+  @HopMetadataProperty(
+      key = "rename_on_output",
+      injectionKey = "RENAME_ON_OUTPUT",
+      injectionKeyDescription = "SimpleMapping.Injection.RENAME_ON_OUTPUT")
   private boolean renamingOnOutput;
 
   /**
diff --git 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingParameters.java
 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingParameters.java
index bcf08f504e..a3f7bebaeb 100644
--- 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingParameters.java
+++ 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingParameters.java
@@ -30,11 +30,17 @@ import 
org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
  */
 public class MappingParameters implements Cloneable {
 
-  @HopMetadataProperty(key = "variablemapping")
+  @HopMetadataProperty(
+      key = "variablemapping",
+      injectionGroupKey = "MAPPINGS",
+      injectionGroupDescription = "SimpleMapping.Injection.Group.MAPPINGS")
   private List<MappingVariableMapping> variableMappings;
 
   /** This flag causes the sub-transformation to inherit all variables from 
the parent */
-  @HopMetadataProperty(key = "inherit_all_vars")
+  @HopMetadataProperty(
+      key = "inherit_all_vars",
+      injectionKey = "INHERIT_ALL_VARIABLES",
+      injectionKeyDescription = 
"SimpleMapping.Injection.INHERIT_ALL_VARIABLES")
   private boolean inheritingAllVariables;
 
   public MappingParameters() {
diff --git 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingValueRename.java
 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingValueRename.java
index a2a44d66a4..65f7948f76 100644
--- 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingValueRename.java
+++ 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingValueRename.java
@@ -20,10 +20,16 @@ package org.apache.hop.pipeline.transforms.mapping;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 
 public class MappingValueRename implements Cloneable {
-  @HopMetadataProperty(key = "parent")
+  @HopMetadataProperty(
+      key = "parent",
+      injectionKey = "SOURCE_FIELD",
+      injectionKeyDescription = "SimpleMapping.Injection.SOURCE_FIELD")
   private String sourceValueName;
 
-  @HopMetadataProperty(key = "child")
+  @HopMetadataProperty(
+      key = "child",
+      injectionKey = "TARGET_FIELD",
+      injectionKeyDescription = "SimpleMapping.Injection.TARGET_FIELD")
   private String targetValueName;
 
   public MappingValueRename() {}
diff --git 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingVariableMapping.java
 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingVariableMapping.java
index 6361698031..c3d3d887a4 100644
--- 
a/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingVariableMapping.java
+++ 
b/plugins/transforms/mapping/src/main/java/org/apache/hop/pipeline/transforms/mapping/MappingVariableMapping.java
@@ -21,10 +21,16 @@ package org.apache.hop.pipeline.transforms.mapping;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 
 public class MappingVariableMapping implements Cloneable {
-  @HopMetadataProperty(key = "variable")
+  @HopMetadataProperty(
+      key = "variable",
+      injectionKey = "VARIABLE",
+      injectionKeyDescription = "SimpleMapping.Injection.VARIABLE")
   private String name;
 
-  @HopMetadataProperty(key = "input")
+  @HopMetadataProperty(
+      key = "input",
+      injectionKey = "VALUE",
+      injectionKeyDescription = "SimpleMapping.Injection.VALUE")
   private String value;
 
   public MappingVariableMapping() {}
diff --git 
a/plugins/transforms/mapping/src/main/resources/org/apache/hop/pipeline/transforms/mapping/messages/messages_en_US.properties
 
b/plugins/transforms/mapping/src/main/resources/org/apache/hop/pipeline/transforms/mapping/messages/messages_en_US.properties
index 9f7d0e88e8..5d4d0a26d3 100644
--- 
a/plugins/transforms/mapping/src/main/resources/org/apache/hop/pipeline/transforms/mapping/messages/messages_en_US.properties
+++ 
b/plugins/transforms/mapping/src/main/resources/org/apache/hop/pipeline/transforms/mapping/messages/messages_en_US.properties
@@ -155,4 +155,16 @@ SimpleMappingMeta.ReferencedObject.Description=Mapping 
(sub-pipeline)
 SimpleMappingDialog.FilenameMissing.Header=Warning
 SimpleMappingDialog.FilenameMissing.Message=The mapping pipeline filename is 
empty.
 SimpleMappingDialog.SelfReference.Header=Warning
-SimpleMappingDialog.SelfReference.Message=This pipeline can''t map to itself. 
Please choose another pipeline. 
\ No newline at end of file
+SimpleMappingDialog.SelfReference.Message=This pipeline can''t map to itself. 
Please choose another pipeline.
+SimpleMapping.Injection.SOURCE_TRANSFORM=Source transform name
+SimpleMapping.Injection.TARGET_TRANSFORM=Target transform name
+SimpleMapping.Injection.DESCRIPTION=Mapping description
+SimpleMapping.Injection.MAIN_PATH=Main data path
+SimpleMapping.Injection.RENAME_ON_OUTPUT=Update mapped field names downstream
+SimpleMapping.Injection.SOURCE_FIELD=Source field name
+SimpleMapping.Injection.TARGET_FIELD=Target field name
+SimpleMapping.Injection.INHERIT_ALL_VARIABLES=Inherit all variables from the 
parent pipeline
+SimpleMapping.Injection.VARIABLE=Mapping parameter name
+SimpleMapping.Injection.VALUE=Mapping parameter value
+SimpleMapping.Injection.Group.FIELDS=Field mappings
+SimpleMapping.Injection.Group.MAPPINGS=Parameter mappings 
\ No newline at end of file
diff --git 
a/plugins/transforms/mapping/src/test/java/org/apache/hop/pipeline/transforms/mapping/SimpleMappingMetaInjectionTest.java
 
b/plugins/transforms/mapping/src/test/java/org/apache/hop/pipeline/transforms/mapping/SimpleMappingMetaInjectionTest.java
new file mode 100644
index 0000000000..bdc6617223
--- /dev/null
+++ 
b/plugins/transforms/mapping/src/test/java/org/apache/hop/pipeline/transforms/mapping/SimpleMappingMetaInjectionTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.hop.pipeline.transforms.mapping;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.injection.bean.BeanInjectionInfo;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.RowBuffer;
+import org.apache.hop.core.row.RowMetaBuilder;
+import org.apache.hop.metadata.inject.HopMetadataInjector;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class SimpleMappingMetaInjectionTest {
+  @BeforeEach
+  void setUp() throws Exception {
+    HopEnvironment.init();
+    PluginRegistry.init();
+  }
+
+  @Test
+  void injectionKeysAreUnique() {
+    BeanInjectionInfo<SimpleMappingMeta> info = new 
BeanInjectionInfo<>(SimpleMappingMeta.class);
+    Set<String> keys = info.getProperties().keySet();
+
+    assertTrue(keys.contains("filename"));
+    assertTrue(keys.contains("runConfiguration"));
+    assertTrue(keys.contains("INPUT_SOURCE_TRANSFORM"));
+    assertTrue(keys.contains("INPUT_TARGET_TRANSFORM"));
+    assertTrue(keys.contains("OUTPUT_SOURCE_TRANSFORM"));
+    assertTrue(keys.contains("OUTPUT_TARGET_TRANSFORM"));
+    assertTrue(keys.contains("INPUT_SOURCE_FIELD"));
+    assertTrue(keys.contains("OUTPUT_SOURCE_FIELD"));
+    assertTrue(keys.contains("PARAMETERS_INHERIT_ALL_VARIABLES"));
+    assertTrue(keys.contains("PARAMETERS_VARIABLE"));
+    assertTrue(keys.contains("PARAMETERS_VALUE"));
+    assertFalse(keys.contains("input_transform"));
+    assertFalse(keys.contains("output_transform"));
+
+    assertEquals(keys.size(), new HashSet<>(keys).size());
+    assertEquals("INPUT_FIELDS", 
info.getProperties().get("INPUT_SOURCE_FIELD").getGroupKey());
+    assertEquals("OUTPUT_FIELDS", 
info.getProperties().get("OUTPUT_SOURCE_FIELD").getGroupKey());
+    assertEquals(
+        "PARAMETERS_MAPPINGS", 
info.getProperties().get("PARAMETERS_VARIABLE").getGroupKey());
+  }
+
+  @Test
+  void injectsInputAndOutputIndependently() throws Exception {
+    SimpleMappingMeta meta = new SimpleMappingMeta();
+
+    Map<String, Object> injectionKeyMap = new HashMap<>();
+    injectionKeyMap.put("filename", "child.hpl");
+    injectionKeyMap.put("INPUT_SOURCE_TRANSFORM", "Reader");
+    injectionKeyMap.put("INPUT_TARGET_TRANSFORM", "Mapping input");
+    injectionKeyMap.put("OUTPUT_SOURCE_TRANSFORM", "Mapping output");
+    injectionKeyMap.put("OUTPUT_TARGET_TRANSFORM", "Writer");
+    injectionKeyMap.put("INPUT_MAIN_PATH", "Y");
+    injectionKeyMap.put("OUTPUT_MAIN_PATH", "N");
+    injectionKeyMap.put("INPUT_RENAME_ON_OUTPUT", "Y");
+    injectionKeyMap.put("PARAMETERS_INHERIT_ALL_VARIABLES", "N");
+
+    Map<String, RowBuffer> injectionGroupMap = new HashMap<>();
+    RowBuffer inputFields = new RowBuffer();
+    inputFields.setRowMeta(
+        new RowMetaBuilder()
+            .addString("INPUT_SOURCE_FIELD")
+            .addString("INPUT_TARGET_FIELD")
+            .build());
+    inputFields.addRow("a", "fieldA");
+    inputFields.addRow("b", "fieldB");
+    injectionGroupMap.put("INPUT_FIELDS", inputFields);
+
+    RowBuffer outputFields = new RowBuffer();
+    outputFields.setRowMeta(
+        new RowMetaBuilder()
+            .addString("OUTPUT_SOURCE_FIELD")
+            .addString("OUTPUT_TARGET_FIELD")
+            .build());
+    outputFields.addRow("fieldSum", "sum");
+    injectionGroupMap.put("OUTPUT_FIELDS", outputFields);
+
+    RowBuffer parameters = new RowBuffer();
+    parameters.setRowMeta(
+        new RowMetaBuilder()
+            .addString("PARAMETERS_VARIABLE")
+            .addString("PARAMETERS_VALUE")
+            .build());
+    parameters.addRow("LIMIT", "100");
+    injectionGroupMap.put("PARAMETERS_MAPPINGS", parameters);
+
+    HopMetadataInjector.inject(
+        new MemoryMetadataProvider(), meta, injectionKeyMap, 
injectionGroupMap);
+
+    assertEquals("child.hpl", meta.getFilename());
+    assertEquals("Reader", meta.getInputMapping().getInputTransformName());
+    assertEquals("Mapping input", 
meta.getInputMapping().getOutputTransformName());
+    assertEquals("Mapping output", 
meta.getOutputMapping().getInputTransformName());
+    assertEquals("Writer", meta.getOutputMapping().getOutputTransformName());
+    assertTrue(meta.getInputMapping().isMainDataPath());
+    assertFalse(meta.getOutputMapping().isMainDataPath());
+    assertTrue(meta.getInputMapping().isRenamingOnOutput());
+    assertFalse(meta.getMappingParameters().isInheritingAllVariables());
+
+    assertEquals(2, meta.getInputMapping().getValueRenames().size());
+    assertEquals("a", 
meta.getInputMapping().getValueRenames().get(0).getSourceValueName());
+    assertEquals("fieldA", 
meta.getInputMapping().getValueRenames().get(0).getTargetValueName());
+    assertEquals("b", 
meta.getInputMapping().getValueRenames().get(1).getSourceValueName());
+    assertEquals("fieldB", 
meta.getInputMapping().getValueRenames().get(1).getTargetValueName());
+
+    assertEquals(1, meta.getOutputMapping().getValueRenames().size());
+    assertEquals("fieldSum", 
meta.getOutputMapping().getValueRenames().get(0).getSourceValueName());
+    assertEquals("sum", 
meta.getOutputMapping().getValueRenames().get(0).getTargetValueName());
+
+    assertEquals(1, meta.getMappingParameters().getVariableMappings().size());
+    assertEquals("LIMIT", 
meta.getMappingParameters().getVariableMappings().get(0).getName());
+    assertEquals("100", 
meta.getMappingParameters().getVariableMappings().get(0).getValue());
+  }
+}

Reply via email to