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 d7c5170bc0 issue #1872 (UDJE XML cleanup, MDI integration test) (#6796)
d7c5170bc0 is described below

commit d7c5170bc0d104bff9a5b0c856f05b198aa7d732
Author: Matt Casters <[email protected]>
AuthorDate: Tue Mar 17 09:27:53 2026 +0100

    issue #1872 (UDJE XML cleanup, MDI integration test) (#6796)
---
 .../0042-user-defined-java-expression-template.hpl |  62 +++++
 .../0042-user-defined-java-expression-validate.hpl | 210 ++++++++++++++++
 .../mdi/0042-user-defined-java-expression.hpl      | 264 +++++++++++++++++++++
 .../golden-user-defined-java-expression.csv        |   4 +
 integration-tests/mdi/files/janino.xml             |  43 ++++
 .../mdi/main-0042-user-defined-java-expression.hwf |  58 +++++
 .../golden-user-defined-java-expression.json       |  56 +++++
 ...user-defined-java-expression-validate UNIT.json |  48 ++++
 .../hop/pipeline/transforms/janino/Janino.java     |  43 ++--
 .../pipeline/transforms/janino/JaninoDialog.java   |  51 ++--
 .../hop/pipeline/transforms/janino/JaninoMeta.java |  90 ++-----
 .../transforms/janino/JaninoMetaFunction.java      | 194 ++++-----------
 .../pipeline/transforms/janino/JaninoMetaTest.java | 131 +++++-----
 .../janino/src/test/resources/janino.xml           |  43 ++++
 14 files changed, 965 insertions(+), 332 deletions(-)

diff --git 
a/integration-tests/mdi/0042-user-defined-java-expression-template.hpl 
b/integration-tests/mdi/0042-user-defined-java-expression-template.hpl
new file mode 100644
index 0000000000..e143089752
--- /dev/null
+++ b/integration-tests/mdi/0042-user-defined-java-expression-template.hpl
@@ -0,0 +1,62 @@
+<?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>0042-user-defined-java-expression-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/03/16 17:49:10.301</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 17:49:10.301</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+  </order>
+  <transform>
+    <name>User defined Java expression</name>
+    <type>Janino</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/mdi/0042-user-defined-java-expression-validate.hpl 
b/integration-tests/mdi/0042-user-defined-java-expression-validate.hpl
new file mode 100644
index 0000000000..68d1aeeed9
--- /dev/null
+++ b/integration-tests/mdi/0042-user-defined-java-expression-validate.hpl
@@ -0,0 +1,210 @@
+<?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>0042-user-defined-java-expression-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/03/16 17:52:25.666</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 17:52:25.666</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>/tmp/janino.hpl</from>
+      <to>Validate</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>/tmp/janino.hpl</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>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>field_name</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>field_name</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>formula_string</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>formula_string</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_type</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>value_type</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_length</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>Integer</type>
+        <xPath>value_length</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_precision</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>Integer</type>
+        <xPath>value_precision</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>replace_field</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>replace_field</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${java.io.tmpdir}/janino.hpl</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/pipeline/transform/formula</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>112</xloc>
+      <yloc>112</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>288</xloc>
+      <yloc>112</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0042-user-defined-java-expression.hpl 
b/integration-tests/mdi/0042-user-defined-java-expression.hpl
new file mode 100644
index 0000000000..825d26273b
--- /dev/null
+++ b/integration-tests/mdi/0042-user-defined-java-expression.hpl
@@ -0,0 +1,264 @@
+<?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>0042-user-defined-java-expression</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/03/16 17:49:35.033</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 17:49:35.033</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>files/janino.xml fields</from>
+      <to>0042-user-defined-java-expression-template.hpl</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>files/janino.xml 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>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>field_name</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>field_name</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>formula_string</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>formula_string</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_type</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>value_type</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_length</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>Integer</type>
+        <xPath>value_length</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>value_precision</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>Integer</type>
+        <xPath>value_precision</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>replace_field</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>replace_field</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/janino.xml</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/transform/formula</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>112</xloc>
+      <yloc>64</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>0042-user-defined-java-expression-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}/0042-user-defined-java-expression-template.hpl</filename>
+    <run_configuration>local</run_configuration>
+    <source_transform/>
+    <source_output_fields>    </source_output_fields>
+    <target_file>${java.io.tmpdir}/janino.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>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>REPLACE_FIELD</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>replace_field</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>FIELD_NAME</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>field_name</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>VALUE_LENGTH</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>value_length</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>VALUE_PRECISION</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>value_precision</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>VALUE_TYPE</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>value_type</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>User defined Java 
expression</target_transform_name>
+        <target_attribute_key>FIELD_FORMULA</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>files/janino.xml fields</source_transform>
+        <source_field>formula_string</source_field>
+      </mapping>
+    </mappings>
+    <attributes/>
+    <GUI>
+      <xloc>320</xloc>
+      <yloc>64</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/mdi/datasets/golden-user-defined-java-expression.csv 
b/integration-tests/mdi/datasets/golden-user-defined-java-expression.csv
new file mode 100644
index 0000000000..f900d15c47
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-user-defined-java-expression.csv
@@ -0,0 +1,4 @@
+field_name,formula_string,value_type,value_length,value_precision,replace_field
+f1,expression1,String,100,-1,replace1
+f2,expression2,Integer,7,-1,replace2
+f3,expression3,Number,9,2,replace3
diff --git a/integration-tests/mdi/files/janino.xml 
b/integration-tests/mdi/files/janino.xml
new file mode 100644
index 0000000000..6abc2f9d84
--- /dev/null
+++ b/integration-tests/mdi/files/janino.xml
@@ -0,0 +1,43 @@
+<?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>
+    <formula>
+        <field_name>f1</field_name>
+        <formula_string>expression1</formula_string>
+        <value_type>String</value_type>
+        <value_length>100</value_length>
+        <value_precision>-1</value_precision>
+        <replace_field>replace1</replace_field>
+    </formula>
+    <formula>
+        <field_name>f2</field_name>
+        <formula_string>expression2</formula_string>
+        <value_type>Integer</value_type>
+        <value_length>7</value_length>
+        <value_precision>-1</value_precision>
+        <replace_field>replace2</replace_field>
+    </formula>
+    <formula>
+        <field_name>f3<Get rows from result XML cleanup/field_name>
+        <formula_string>expression3</formula_string>
+        <value_type>Number</value_type>
+        <value_length>9</value_length>
+        <value_precision>2</value_precision>
+        <replace_field>replace3</replace_field>
+    </formula>
+</transform>
diff --git a/integration-tests/mdi/main-0042-user-defined-java-expression.hwf 
b/integration-tests/mdi/main-0042-user-defined-java-expression.hwf
new file mode 100644
index 0000000000..bbd6b91986
--- /dev/null
+++ b/integration-tests/mdi/main-0042-user-defined-java-expression.hwf
@@ -0,0 +1,58 @@
+<?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-0042-user-defined-java-expression</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/03/16 17:48:47.066</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/03/16 17:48:47.066</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>80</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-user-defined-java-expression.json
 
b/integration-tests/mdi/metadata/dataset/golden-user-defined-java-expression.json
new file mode 100644
index 0000000000..2a93f28c5b
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/dataset/golden-user-defined-java-expression.json
@@ -0,0 +1,56 @@
+{
+  "base_filename": "golden-user-defined-java-expression.csv",
+  "name": "golden-user-defined-java-expression",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "field_name",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "formula_string",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "value_type",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": 0,
+      "field_name": "value_length",
+      "field_format": "####0;-####0"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": 0,
+      "field_name": "value_precision",
+      "field_format": "####0;-####0"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "replace_field",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git 
a/integration-tests/mdi/metadata/unit-test/0042-user-defined-java-expression-validate
 UNIT.json 
b/integration-tests/mdi/metadata/unit-test/0042-user-defined-java-expression-validate
 UNIT.json
new file mode 100644
index 0000000000..c3d9d3ab04
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/unit-test/0042-user-defined-java-expression-validate
 UNIT.json     
@@ -0,0 +1,48 @@
+{
+  "database_replacements": [],
+  "autoOpening": true,
+  "description": "",
+  "persist_filename": "",
+  "test_type": "UNIT_TEST",
+  "variableValues": [],
+  "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "field_name",
+          "data_set_field": "field_name"
+        },
+        {
+          "transform_field": "formula_string",
+          "data_set_field": "formula_string"
+        },
+        {
+          "transform_field": "value_type",
+          "data_set_field": "value_type"
+        },
+        {
+          "transform_field": "value_length",
+          "data_set_field": "value_length"
+        },
+        {
+          "transform_field": "value_precision",
+          "data_set_field": "value_precision"
+        },
+        {
+          "transform_field": "replace_field",
+          "data_set_field": "replace_field"
+        }
+      ],
+      "field_order": [
+        "field_name"
+      ],
+      "data_set_name": "golden-user-defined-java-expression",
+      "transform_name": "Validate"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0042-user-defined-java-expression-validate UNIT",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0042-user-defined-java-expression-validate.hpl"
+}
\ No newline at end of file
diff --git 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/Janino.java
 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/Janino.java
index 38c607de35..00bff1e971 100644
--- 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/Janino.java
+++ 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/Janino.java
@@ -72,17 +72,17 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
 
       // Calculate replace indexes...
       //
-      data.replaceIndex = new int[meta.getFormula().length];
-      data.returnType = new IValueMeta[meta.getFormula().length];
-      for (int i = 0; i < meta.getFormula().length; i++) {
-        JaninoMetaFunction fn = meta.getFormula()[i];
-        data.returnType[i] = 
ValueMetaFactory.createValueMeta(fn.getValueType());
-        if (!Utils.isEmpty(fn.getReplaceField())) {
-          data.replaceIndex[i] = 
getInputRowMeta().indexOfValue(fn.getReplaceField());
+      data.replaceIndex = new int[meta.getFunctions().size()];
+      data.returnType = new IValueMeta[meta.getFunctions().size()];
+      for (int i = 0; i < meta.getFunctions().size(); i++) {
+        JaninoMetaFunction function = meta.getFunctions().get(i);
+        data.returnType[i] = 
ValueMetaFactory.createValueMeta(function.getValueType());
+        if (!Utils.isEmpty(function.getReplaceField())) {
+          data.replaceIndex[i] = 
getInputRowMeta().indexOfValue(function.getReplaceField());
           if (data.replaceIndex[i] < 0) {
             throw new HopException(
                 "Unknown field specified to replace with a formula result: ["
-                    + fn.getReplaceField()
+                    + function.getReplaceField()
                     + "]");
           }
         } else {
@@ -128,15 +128,15 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
       // Initialize evaluators etc. Only do it once.
       //
       if (data.expressionEvaluators == null) {
-        data.expressionEvaluators = new 
ExpressionEvaluator[meta.getFormula().length];
+        data.expressionEvaluators = new 
ExpressionEvaluator[meta.getFunctions().size()];
         data.argumentIndexes = new ArrayList<>();
 
-        for (int i = 0; i < meta.getFormula().length; i++) {
+        for (int i = 0; i < meta.getFunctions().size(); i++) {
           List<Integer> argIndexes = new ArrayList<>();
           data.argumentIndexes.add(argIndexes);
         }
 
-        for (int m = 0; m < meta.getFormula().length; m++) {
+        for (int m = 0; m < meta.getFunctions().size(); m++) {
           List<Integer> argIndexes = data.argumentIndexes.get(m);
           List<String> parameterNames = new ArrayList<>();
           List<Class<?>> parameterTypes = new ArrayList<>();
@@ -147,7 +147,7 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
 
             // See if the value is being used in a formula...
             //
-            if 
(meta.getFormula()[m].getFormula().contains(valueMeta.getName())) {
+            if 
(meta.getFunctions().get(m).getFormula().contains(valueMeta.getName())) {
               // If so, add it to the indexes...
               argIndexes.add(i);
 
@@ -156,8 +156,8 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
             }
           }
 
-          JaninoMetaFunction fn = meta.getFormula()[m];
-          if (!Utils.isEmpty(fn.getFieldName())) {
+          JaninoMetaFunction function = meta.getFunctions().get(m);
+          if (!Utils.isEmpty(function.getFieldName())) {
 
             // Create the expression evaluator: is relatively slow so we do it 
only for the first
             // row...
@@ -168,8 +168,7 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
             FunctionLib functionLib = new FunctionLib();
             data.expressionEvaluators[m] = new ExpressionEvaluator();
             data.expressionEvaluators[m].setParameters(
-                parameterNames.toArray(new String[parameterNames.size()]),
-                parameterTypes.toArray(new Class<?>[parameterTypes.size()]));
+                parameterNames.toArray(new String[0]), 
parameterTypes.toArray(new Class<?>[0]));
             data.expressionEvaluators[m].setReturnType(Object.class);
             data.expressionEvaluators[m].setThrownExceptions(new Class<?>[] 
{Exception.class});
             data.expressionEvaluators[m].setParentClassLoader(loader);
@@ -177,22 +176,22 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
 
             // Validate Formula
             JaninoCheckerUtil janinoCheckerUtil = new JaninoCheckerUtil();
-            List<String> codeCheck = 
janinoCheckerUtil.checkCode(fn.getFormula());
+            List<String> codeCheck = 
janinoCheckerUtil.checkCode(function.getFormula());
             if (!codeCheck.isEmpty()) {
               throw new HopException("Script contains code that is not allowed 
: " + codeCheck);
             }
 
-            data.expressionEvaluators[m].cook(fn.getFormula());
+            data.expressionEvaluators[m].cook(function.getFormula());
           } else {
             throw new HopException(
                 "Unable to find field name for formula ["
-                    + (!StringUtil.isEmpty(fn.getFormula()) ? fn.getFormula() 
: "")
+                    + (!StringUtil.isEmpty(function.getFormula()) ? 
function.getFormula() : "")
                     + "]");
           }
         }
       }
 
-      for (int i = 0; i < meta.getFormula().length; i++) {
+      for (int i = 0; i < meta.getFunctions().size(); i++) {
         List<Integer> argumentIndexes = data.argumentIndexes.get(i);
 
         // This method can only accept the specified number of values...
@@ -222,9 +221,9 @@ public class Janino extends BaseTransform<JaninoMeta, 
JaninoData> {
                     PKG,
                     "Janino.Error.ValueTypeMismatch",
                     valueMeta.getTypeDesc(),
-                    meta.getFormula()[i].getFieldName(),
+                    meta.getFunctions().get(i).getFieldName(),
                     formulaResult.getClass(),
-                    meta.getFormula()[i].getFormula()));
+                    meta.getFunctions().get(i).getFormula()));
           }
         }
 
diff --git 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoDialog.java
 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoDialog.java
index 2b6984d113..a340064cd4 100644
--- 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoDialog.java
+++ 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoDialog.java
@@ -83,7 +83,7 @@ public class JaninoDialog extends BaseTransformDialog {
     fdlFields.top = new FormAttachment(wSpacer, margin);
     wlFields.setLayoutData(fdlFields);
 
-    final int FieldsRows = currentMeta.getFormula() != null ? 
currentMeta.getFormula().length : 1;
+    final int nrFields = currentMeta.getFunctions().size();
 
     colinf =
         new ColumnInfo[] {
@@ -119,7 +119,7 @@ public class JaninoDialog extends BaseTransformDialog {
             shell,
             SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI,
             colinf,
-            FieldsRows,
+            nrFields,
             null,
             props);
 
@@ -214,20 +214,20 @@ public class JaninoDialog extends BaseTransformDialog {
 
   /** Copy information from the meta-data currentMeta to the dialog fields. */
   public void getData() {
-    if (currentMeta.getFormula() != null) {
-      for (int i = 0; i < currentMeta.getFormula().length; i++) {
-        JaninoMetaFunction fn = currentMeta.getFormula()[i];
+    if (currentMeta.getFunctions() != null) {
+      for (int i = 0; i < currentMeta.getFunctions().size(); i++) {
+        JaninoMetaFunction function = currentMeta.getFunctions().get(i);
         TableItem item = wFields.table.getItem(i);
-        item.setText(1, Const.NVL(fn.getFieldName(), ""));
-        item.setText(2, Const.NVL(fn.getFormula(), ""));
-        item.setText(3, 
Const.NVL(ValueMetaFactory.getValueMetaName(fn.getValueType()), ""));
-        if (fn.getValueLength() >= 0) {
-          item.setText(4, "" + fn.getValueLength());
+        item.setText(1, Const.NVL(function.getFieldName(), ""));
+        item.setText(2, Const.NVL(function.getFormula(), ""));
+        item.setText(3, 
Const.NVL(ValueMetaFactory.getValueMetaName(function.getValueType()), ""));
+        if (function.getValueLength() >= 0) {
+          item.setText(4, "" + function.getValueLength());
         }
-        if (fn.getValuePrecision() >= 0) {
-          item.setText(5, "" + fn.getValuePrecision());
+        if (function.getValuePrecision() >= 0) {
+          item.setText(5, "" + function.getValuePrecision());
         }
-        item.setText(6, Const.NVL(fn.getReplaceField(), ""));
+        item.setText(6, Const.NVL(function.getReplaceField(), ""));
       }
     }
 
@@ -265,21 +265,16 @@ public class JaninoDialog extends BaseTransformDialog {
 
     transformName = wTransformName.getText(); // return value
 
-    currentMeta.allocate(wFields.nrNonEmpty());
-
-    for (int i = 0; i < nrNonEmptyFields; i++) {
-      TableItem item = wFields.getNonEmpty(i);
-
-      String fieldName = item.getText(1);
-      String formula = item.getText(2);
-      int valueType = ValueMetaFactory.getIdForValueMeta(item.getText(3));
-      int valueLength = Const.toInt(item.getText(4), -1);
-      int valuePrecision = Const.toInt(item.getText(5), -1);
-      String replaceField = item.getText(6);
-
-      currentMeta.getFormula()[i] =
-          new JaninoMetaFunction(
-              fieldName, formula, valueType, valueLength, valuePrecision, 
replaceField);
+    currentMeta.getFunctions().clear();
+    for (TableItem item : wFields.getNonEmptyItems()) {
+      JaninoMetaFunction function = new JaninoMetaFunction();
+      function.setFieldName(item.getText(1));
+      function.setFormula(item.getText(2));
+      
function.setValueType(ValueMetaFactory.getIdForValueMeta(item.getText(3)));
+      function.setValueLength(Const.toInt(item.getText(4), -1));
+      function.setValuePrecision(Const.toInt(item.getText(5), -1));
+      function.setReplaceField(item.getText(6));
+      currentMeta.getFunctions().add(function);
     }
 
     if (!originalMeta.equals(currentMeta)) {
diff --git 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMeta.java
 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMeta.java
index 9ea918f174..4e3060959f 100644
--- 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMeta.java
+++ 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMeta.java
@@ -16,29 +16,26 @@
  */
 package org.apache.hop.pipeline.transforms.janino;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.CheckResult;
-import org.apache.hop.core.Const;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.Transform;
 import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.exception.HopXmlException;
-import org.apache.hop.core.injection.InjectionDeep;
 import org.apache.hop.core.injection.InjectionSupported;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.core.xml.XmlHandler;
 import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
-import org.w3c.dom.Node;
 
 @InjectionSupported(
     localizationPrefix = "Janino.Injection.",
@@ -53,82 +50,31 @@ import org.w3c.dom.Node;
     categoryDescription = 
"i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Scripting",
     keywords = "i18n::JaninoMeta.keyword",
     documentationUrl = "/pipeline/transforms/userdefinedjavaexpression.html")
+@Getter
+@Setter
 public class JaninoMeta extends BaseTransformMeta<Janino, JaninoData> {
   private static final Class<?> PKG = JaninoMeta.class;
 
   /** The formula calculations to be performed */
-  @InjectionDeep private JaninoMetaFunction[] formula;
+  @HopMetadataProperty(
+      key = "formula",
+      injectionGroupKey = "FORMULA",
+      injectionGroupDescription = "Janino.Injection.FORMULA")
+  private List<JaninoMetaFunction> functions;
 
   public JaninoMeta() {
-    super(); // allocate BaseTransformMeta
+    super();
+    this.functions = new ArrayList<>();
   }
 
-  public JaninoMetaFunction[] getFormula() {
-    return formula;
-  }
-
-  public void setFormula(JaninoMetaFunction[] calcTypes) {
-    this.formula = calcTypes;
-  }
-
-  public void allocate(int nrCalcs) {
-    formula = new JaninoMetaFunction[nrCalcs];
-  }
-
-  @Override
-  public void loadXml(Node transformNode, IHopMetadataProvider 
metadataProvider)
-      throws HopXmlException {
-    int nrCalcs = XmlHandler.countNodes(transformNode, 
JaninoMetaFunction.XML_TAG);
-    allocate(nrCalcs);
-    for (int i = 0; i < nrCalcs; i++) {
-      Node calcnode = XmlHandler.getSubNodeByNr(transformNode, 
JaninoMetaFunction.XML_TAG, i);
-      formula[i] = new JaninoMetaFunction(calcnode);
-    }
-  }
-
-  @Override
-  public String getXml() {
-    StringBuilder retval = new StringBuilder();
-
-    if (formula != null) {
-      for (JaninoMetaFunction janinoMetaFunction : formula) {
-        retval.append("       " + janinoMetaFunction.getXml() + Const.CR);
-      }
-    }
-
-    return retval.toString();
-  }
-
-  public boolean equals(Object obj) {
-    if (obj != null && (obj.getClass().equals(this.getClass()))) {
-      JaninoMeta m = (JaninoMeta) obj;
-      return Objects.equals(getXml(), m.getXml());
-    }
-    return false;
-  }
-
-  @Override
-  public int hashCode() {
-    return Arrays.hashCode(formula);
+  public JaninoMeta(JaninoMeta m) {
+    this();
+    m.functions.forEach(f -> this.functions.add(new JaninoMetaFunction(f)));
   }
 
   @Override
   public Object clone() {
-    JaninoMeta retval = (JaninoMeta) super.clone();
-    if (formula != null) {
-      retval.allocate(formula.length);
-      for (int i = 0; i < formula.length; i++) {
-        retval.getFormula()[i] = (JaninoMetaFunction) formula[i].clone();
-      }
-    } else {
-      retval.allocate(0);
-    }
-    return retval;
-  }
-
-  @Override
-  public void setDefault() {
-    formula = new JaninoMetaFunction[0];
+    return new JaninoMeta(this);
   }
 
   @Override
@@ -140,7 +86,7 @@ public class JaninoMeta extends BaseTransformMeta<Janino, 
JaninoData> {
       IVariables variables,
       IHopMetadataProvider metadataProvider)
       throws HopTransformException {
-    for (JaninoMetaFunction fn : formula) {
+    for (JaninoMetaFunction fn : functions) {
       if (Utils.isEmpty(fn.getReplaceField())) {
         // Not replacing a field.
         if (!Utils.isEmpty(fn.getFieldName())) {
diff --git 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaFunction.java
 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaFunction.java
index 632bb6cac3..6668882505 100644
--- 
a/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaFunction.java
+++ 
b/plugins/transforms/janino/src/main/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaFunction.java
@@ -17,60 +17,67 @@
 package org.apache.hop.pipeline.transforms.janino;
 
 import java.util.Objects;
-import org.apache.hop.core.Const;
-import org.apache.hop.core.injection.Injection;
-import org.apache.hop.core.row.value.ValueMetaFactory;
-import org.apache.hop.core.xml.XmlHandler;
-import org.w3c.dom.Node;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.hop.core.row.value.ValueMetaBase;
+import org.apache.hop.metadata.api.HopMetadataProperty;
 
+@Getter
+@Setter
 public class JaninoMetaFunction implements Cloneable {
-  public static final String XML_TAG = "formula";
-
-  @Injection(name = "FIELD_NAME", group = "FORMULA")
+  @HopMetadataProperty(
+      key = "field_name",
+      injectionKey = "FIELD_NAME",
+      injectionKeyDescription = "Janino.Injection.FIELD_NAME")
   private String fieldName;
 
-  @Injection(name = "FIELD_FORMULA", group = "FORMULA")
+  @HopMetadataProperty(
+      key = "formula_string",
+      injectionKey = "FIELD_FORMULA",
+      injectionKeyDescription = "Janino.Injection.FIELD_FORMULA")
   private String formula;
 
+  @HopMetadataProperty(
+      key = "value_type",
+      intCodeConverter = ValueMetaBase.ValueTypeCodeConverter.class,
+      injectionKey = "VALUE_TYPE",
+      injectionKeyDescription = "Janino.Injection.VALUE_TYPE")
   private int valueType;
 
-  @Injection(name = "VALUE_LENGTH", group = "FORMULA")
+  @HopMetadataProperty(
+      key = "value_length",
+      injectionKey = "VALUE_LENGTH",
+      injectionKeyDescription = "Janino.Injection.VALUE_LENGTH")
   private int valueLength;
 
-  @Injection(name = "VALUE_PRECISION", group = "FORMULA")
+  @HopMetadataProperty(
+      key = "value_precision",
+      injectionKey = "VALUE_PRECISION",
+      injectionKeyDescription = "Janino.Injection.VALUE_PRECISION")
   private int valuePrecision;
 
-  @Injection(name = "REPLACE_FIELD", group = "FORMULA")
+  @HopMetadataProperty(
+      key = "replace_field",
+      injectionKey = "REPLACE_FIELD",
+      injectionKeyDescription = "Janino.Injection.REPLACE_FIELD")
   private String replaceField;
 
   public JaninoMetaFunction() {}
 
-  /**
-   * @param fieldName
-   * @param formula
-   * @param valueType
-   * @param valueLength
-   * @param valuePrecision
-   */
-  public JaninoMetaFunction(
-      String fieldName,
-      String formula,
-      int valueType,
-      int valueLength,
-      int valuePrecision,
-      String replaceField) {
-    this.fieldName = fieldName;
-    this.formula = formula;
-    this.valueType = valueType;
-    this.valueLength = valueLength;
-    this.valuePrecision = valuePrecision;
-    this.replaceField = replaceField;
+  public JaninoMetaFunction(JaninoMetaFunction f) {
+    this();
+    this.fieldName = f.fieldName;
+    this.formula = f.formula;
+    this.valueType = f.valueType;
+    this.valueLength = f.valueLength;
+    this.valuePrecision = f.valuePrecision;
+    this.replaceField = f.replaceField;
   }
 
   public boolean equals(Object obj) {
     if (obj != null && (obj.getClass().equals(this.getClass()))) {
       JaninoMetaFunction mf = (JaninoMetaFunction) obj;
-      return (getXml().equals(mf.getXml()));
+      return fieldName.equals(mf.getFieldName());
     }
 
     return false;
@@ -83,125 +90,6 @@ public class JaninoMetaFunction implements Cloneable {
 
   @Override
   public Object clone() {
-    try {
-      return super.clone();
-    } catch (CloneNotSupportedException e) {
-      return null;
-    }
-  }
-
-  public String getXml() {
-    StringBuilder xml = new StringBuilder();
-
-    xml.append(XmlHandler.openTag(XML_TAG));
-
-    xml.append(XmlHandler.addTagValue("field_name", fieldName));
-    xml.append(XmlHandler.addTagValue("formula_string", formula));
-    xml.append(XmlHandler.addTagValue("value_type", 
ValueMetaFactory.getValueMetaName(valueType)));
-    xml.append(XmlHandler.addTagValue("value_length", valueLength));
-    xml.append(XmlHandler.addTagValue("value_precision", valuePrecision));
-    xml.append(XmlHandler.addTagValue("replace_field", replaceField));
-
-    xml.append(XmlHandler.closeTag(XML_TAG));
-
-    return xml.toString();
-  }
-
-  public JaninoMetaFunction(Node calcnode) {
-    fieldName = XmlHandler.getTagValue(calcnode, "field_name");
-    formula = XmlHandler.getTagValue(calcnode, "formula_string");
-    valueType = 
ValueMetaFactory.getIdForValueMeta(XmlHandler.getTagValue(calcnode, 
"value_type"));
-    valueLength = Const.toInt(XmlHandler.getTagValue(calcnode, 
"value_length"), -1);
-    valuePrecision = Const.toInt(XmlHandler.getTagValue(calcnode, 
"value_precision"), -1);
-    replaceField = XmlHandler.getTagValue(calcnode, "replace_field");
-  }
-
-  /**
-   * @return Returns the fieldName.
-   */
-  public String getFieldName() {
-    return fieldName;
-  }
-
-  /**
-   * @param fieldName The fieldName to set.
-   */
-  public void setFieldName(String fieldName) {
-    this.fieldName = fieldName;
-  }
-
-  /**
-   * @return Returns the valueLength.
-   */
-  public int getValueLength() {
-    return valueLength;
-  }
-
-  /**
-   * @param valueLength The valueLength to set.
-   */
-  public void setValueLength(int valueLength) {
-    this.valueLength = valueLength;
-  }
-
-  /**
-   * @return Returns the valuePrecision.
-   */
-  public int getValuePrecision() {
-    return valuePrecision;
-  }
-
-  /**
-   * @param valuePrecision The valuePrecision to set.
-   */
-  public void setValuePrecision(int valuePrecision) {
-    this.valuePrecision = valuePrecision;
-  }
-
-  /**
-   * @return Returns the valueType.
-   */
-  public int getValueType() {
-    return valueType;
-  }
-
-  /**
-   * @param valueType The valueType to set.
-   */
-  public void setValueType(int valueType) {
-    this.valueType = valueType;
-  }
-
-  @Injection(name = "VALUE_TYPE", group = "FORMULA")
-  public void setValueType(String typeDesc) {
-    this.valueType = ValueMetaFactory.getIdForValueMeta(typeDesc);
-  }
-
-  /**
-   * @return the formula
-   */
-  public String getFormula() {
-    return formula;
-  }
-
-  /**
-   * @param formula the formula to set
-   */
-  public void setFormula(String formula) {
-    this.formula = formula;
-  }
-
-  /**
-   * @return the replaceField
-   */
-  public String getReplaceField() {
-    return replaceField;
-  }
-
-  /**
-   * @param replaceField the replaceField to set
-   */
-  public void setReplaceField(String replaceField) {
-    this.replaceField = replaceField;
+    return new JaninoMetaFunction(this);
   }
 }
diff --git 
a/plugins/transforms/janino/src/test/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaTest.java
 
b/plugins/transforms/janino/src/test/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaTest.java
index d4eadb5718..9eee4ed8ee 100644
--- 
a/plugins/transforms/janino/src/test/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaTest.java
+++ 
b/plugins/transforms/janino/src/test/java/org/apache/hop/pipeline/transforms/janino/JaninoMetaTest.java
@@ -17,75 +17,92 @@
 
 package org.apache.hop.pipeline.transforms.janino;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.UUID;
-import org.apache.hop.core.exception.HopException;
-import org.apache.hop.core.exception.HopPluginException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
 import org.apache.hop.core.plugins.PluginRegistry;
-import org.apache.hop.core.row.value.ValueMetaFactory;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaNumber;
+import org.apache.hop.core.row.value.ValueMetaPlugin;
 import org.apache.hop.core.row.value.ValueMetaPluginType;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.ArrayLoadSaveValidator;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import org.junit.jupiter.api.BeforeAll;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
 
 class JaninoMetaTest {
-  @RegisterExtension
-  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
-
-  @BeforeAll
-  static void setUpBeforeClass() throws HopPluginException {
-    PluginRegistry.addPluginType(ValueMetaPluginType.getInstance());
-    PluginRegistry.init();
+  @BeforeEach
+  void beforeEach() throws Exception {
+    PluginRegistry registry = PluginRegistry.getInstance();
+    String[] classNames = {
+      ValueMetaString.class.getName(), ValueMetaInteger.class.getName(),
+      ValueMetaDate.class.getName(), ValueMetaNumber.class.getName()
+    };
+    for (String className : classNames) {
+      registry.registerPluginClass(className, ValueMetaPluginType.class, 
ValueMetaPlugin.class);
+    }
   }
 
   @Test
-  void testRoundTrip() throws HopException {
-    List<String> attributes = Arrays.asList("formula");
-
-    Map<String, IFieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap 
= new HashMap<>();
-
-    IFieldLoadSaveValidator<JaninoMetaFunction[]> 
janinoMetaFunctionArrayLoadSaveValidator =
-        new ArrayLoadSaveValidator<>(new 
JaninoMetaFunctionFieldLoadSaveValidator(), 25);
+  void testLoadSave() throws Exception {
+    Path path = 
Paths.get(Objects.requireNonNull(getClass().getResource("/janino.xml")).toURI());
+    String xml = Files.readString(path);
+    JaninoMeta meta = new JaninoMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+        JaninoMeta.class,
+        meta,
+        new MemoryMetadataProvider());
 
-    fieldLoadSaveValidatorAttributeMap.put("formula", 
janinoMetaFunctionArrayLoadSaveValidator);
+    validate(meta);
 
-    LoadSaveTester loadSaveTester =
-        new LoadSaveTester(
-            JaninoMeta.class,
-            attributes,
-            new HashMap<>(),
-            new HashMap<>(),
-            fieldLoadSaveValidatorAttributeMap,
-            new HashMap<>());
-
-    loadSaveTester.testSerialization();
+    // Do a round trip:
+    //
+    String xmlCopy =
+        XmlHandler.openTag(TransformMeta.XML_TAG)
+            + XmlMetadataUtil.serializeObjectToXml(meta)
+            + XmlHandler.closeTag(TransformMeta.XML_TAG);
+    JaninoMeta metaCopy = new JaninoMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xmlCopy, TransformMeta.XML_TAG),
+        JaninoMeta.class,
+        metaCopy,
+        new MemoryMetadataProvider());
+    validate(metaCopy);
   }
 
-  public class JaninoMetaFunctionFieldLoadSaveValidator
-      implements IFieldLoadSaveValidator<JaninoMetaFunction> {
-    @Override
-    public JaninoMetaFunction getTestObject() {
-      Random random = new Random();
-      return new JaninoMetaFunction(
-          UUID.randomUUID().toString(),
-          UUID.randomUUID().toString(),
-          random.nextInt(ValueMetaFactory.getAllValueMetaNames().length),
-          random.nextInt(Integer.MAX_VALUE),
-          random.nextInt(Integer.MAX_VALUE),
-          UUID.randomUUID().toString());
-    }
+  private static void validate(JaninoMeta meta) {
+    assertEquals(3, meta.getFunctions().size());
+    JaninoMetaFunction f = meta.getFunctions().getFirst();
+    assertEquals("f1", f.getFieldName());
+    assertEquals("expression1", f.getFormula());
+    assertEquals(IValueMeta.TYPE_STRING, f.getValueType());
+    assertEquals(100, f.getValueLength());
+    assertEquals(-1, f.getValuePrecision());
+    assertEquals("replace1", f.getReplaceField());
 
-    @Override
-    public boolean validateTestObject(JaninoMetaFunction testObject, Object 
actual) {
-      return testObject.equals(actual);
-    }
+    f = meta.getFunctions().get(1);
+    assertEquals("f2", f.getFieldName());
+    assertEquals("expression2", f.getFormula());
+    assertEquals(IValueMeta.TYPE_INTEGER, f.getValueType());
+    assertEquals(7, f.getValueLength());
+    assertEquals(-1, f.getValuePrecision());
+    assertEquals("replace2", f.getReplaceField());
+
+    f = meta.getFunctions().get(2);
+    assertEquals("f3", f.getFieldName());
+    assertEquals("expression3", f.getFormula());
+    assertEquals(IValueMeta.TYPE_NUMBER, f.getValueType());
+    assertEquals(9, f.getValueLength());
+    assertEquals(2, f.getValuePrecision());
+    assertEquals("replace3", f.getReplaceField());
   }
 }
diff --git a/plugins/transforms/janino/src/test/resources/janino.xml 
b/plugins/transforms/janino/src/test/resources/janino.xml
new file mode 100644
index 0000000000..08509604ae
--- /dev/null
+++ b/plugins/transforms/janino/src/test/resources/janino.xml
@@ -0,0 +1,43 @@
+<?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>
+    <formula>
+        <field_name>f1</field_name>
+        <formula_string>expression1</formula_string>
+        <value_type>String</value_type>
+        <value_length>100</value_length>
+        <value_precision>-1</value_precision>
+        <replace_field>replace1</replace_field>
+    </formula>
+    <formula>
+        <field_name>f2</field_name>
+        <formula_string>expression2</formula_string>
+        <value_type>Integer</value_type>
+        <value_length>7</value_length>
+        <value_precision>-1</value_precision>
+        <replace_field>replace2</replace_field>
+    </formula>
+    <formula>
+        <field_name>f3</field_name>
+        <formula_string>expression3</formula_string>
+        <value_type>Number</value_type>
+        <value_length>9</value_length>
+        <value_precision>2</value_precision>
+        <replace_field>replace3</replace_field>
+    </formula>
+</transform>

Reply via email to