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());
+ }
+}