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 b0711a5f87 Cube in/output module merge and code hardening, fixes #8380
(#8381)
b0711a5f87 is described below
commit b0711a5f874e92c67c133a1291a0c983f260c934
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Sep 16 12:31:40 2026 +0200
Cube in/output module merge and code hardening, fixes #8380 (#8381)
---
assemblies/plugins/pom.xml | 8 +-
.../transforms/0050-cube-empty-stream-generate.hpl | 140 ++++++++++++
.../transforms/0050-cube-empty-stream-refresh.hpl | 226 +++++++++++++++++++
.../transforms/0050-cube-empty-stream-verify.hpl | 248 +++++++++++++++++++++
.../transforms/main-0050-cube-empty-stream.hwf | 162 ++++++++++++++
plugins/transforms/{cubeinput => cube}/pom.xml | 4 +-
.../{cubeinput => cube}/src/assembly/assembly.xml | 2 +-
.../pipeline/transforms/cubeinput/CubeInput.java | 0
.../transforms/cubeinput/CubeInputData.java | 0
.../transforms/cubeinput/CubeInputDialog.java | 0
.../transforms/cubeinput/CubeInputMeta.java | 37 ++-
.../pipeline/transforms/cubeoutput/CubeOutput.java | 42 +++-
.../transforms/cubeoutput/CubeOutputData.java | 0
.../transforms/cubeoutput/CubeOutputDialog.java | 0
.../transforms/cubeoutput/CubeOutputMeta.java | 0
.../src/main/resources/cubeinput.svg | 0
.../src/main/resources/cubeoutput.svg | 0
.../cubeinput/messages/messages_de_DE.properties | 0
.../cubeinput/messages/messages_en_US.properties | 4 +-
.../cubeinput/messages/messages_es_AR.properties | 0
.../cubeinput/messages/messages_es_ES.properties | 0
.../cubeinput/messages/messages_fr_FR.properties | 0
.../cubeinput/messages/messages_it_IT.properties | 0
.../cubeinput/messages/messages_ja_JP.properties | 0
.../cubeinput/messages/messages_ko_KR.properties | 0
.../cubeinput/messages/messages_nl_NL.properties | 0
.../cubeinput/messages/messages_no_NO.properties | 0
.../cubeinput/messages/messages_pl_PL.properties | 0
.../cubeinput/messages/messages_pt_BR.properties | 0
.../cubeinput/messages/messages_pt_PT.properties | 0
.../cubeinput/messages/messages_zh_CN.properties | 0
.../cubeoutput/messages/messages_de_DE.properties | 0
.../cubeoutput/messages/messages_en_US.properties | 2 +
.../cubeoutput/messages/messages_es_AR.properties | 0
.../cubeoutput/messages/messages_es_ES.properties | 0
.../cubeoutput/messages/messages_fr_FR.properties | 0
.../cubeoutput/messages/messages_it_IT.properties | 0
.../cubeoutput/messages/messages_ja_JP.properties | 0
.../cubeoutput/messages/messages_ko_KR.properties | 0
.../cubeoutput/messages/messages_nl_NL.properties | 0
.../cubeoutput/messages/messages_no_NO.properties | 0
.../cubeoutput/messages/messages_pl_PL.properties | 0
.../cubeoutput/messages/messages_pt_BR.properties | 0
.../cubeoutput/messages/messages_pt_PT.properties | 0
.../cubeoutput/messages/messages_zh_CN.properties | 0
.../src/main/resources/version.xml | 0
.../transforms/cubeinput/CubeInputMetaTest.java | 27 +++
.../transforms/cubeoutput/CubeOutputMetaTest.java | 0
.../transforms/cubeoutput/CubeOutputTest.java | 147 ++++++++++++
.../transforms/cubeoutput/CubeRoundTripTest.java | 115 ++++++++++
.../src/test/resources/de-serialize-transform.xml | 0
.../src/test/resources/serialize-transform.xml | 0
plugins/transforms/cubeoutput/pom.xml | 32 ---
.../cubeoutput/src/assembly/assembly.xml | 46 ----
.../cubeoutput/src/main/resources/version.xml | 20 --
plugins/transforms/pom.xml | 3 +-
56 files changed, 1125 insertions(+), 140 deletions(-)
diff --git a/assemblies/plugins/pom.xml b/assemblies/plugins/pom.xml
index 4417eb7bbb..0d3dfe9227 100644
--- a/assemblies/plugins/pom.xml
+++ b/assemblies/plugins/pom.xml
@@ -878,13 +878,7 @@
</dependency>
<dependency>
<groupId>org.apache.hop</groupId>
- <artifactId>hop-transform-cubeinput</artifactId>
- <version>${project.version}</version>
- <type>zip</type>
- </dependency>
- <dependency>
- <groupId>org.apache.hop</groupId>
- <artifactId>hop-transform-cubeoutput</artifactId>
+ <artifactId>hop-transform-cube</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
diff --git a/integration-tests/transforms/0050-cube-empty-stream-generate.hpl
b/integration-tests/transforms/0050-cube-empty-stream-generate.hpl
new file mode 100644
index 0000000000..bd6c411925
--- /dev/null
+++ b/integration-tests/transforms/0050-cube-empty-stream-generate.hpl
@@ -0,0 +1,140 @@
+<?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>0050-cube-empty-stream-generate</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/09/15 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/15 10:00:00.000</modified_date>
+ </info>
+ <notepads>
+ <notepad>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorred>201</backgroundcolorred>
+ <bordercolorblue>90</bordercolorblue>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorred>14</bordercolorred>
+ <fontbold>N</fontbold>
+ <fontcolorblue>90</fontcolorblue>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorred>14</fontcolorred>
+ <fontitalic>N</fontitalic>
+ <fontname>Ubuntu</fontname>
+ <fontsize>11</fontsize>
+ <height>26</height>
+ <xloc>64</xloc>
+ <yloc>16</yloc>
+ <note>Writes a cube file with a layout (id, name) but no rows.</note>
+ <width>900</width>
+ </notepad>
+ </notepads>
+ <order>
+ <hop>
+ <from>no rows</from>
+ <to>write empty cube</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>no rows</name>
+ <type>RowGenerator</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <name>id</name>
+ <type>Integer</type>
+ <format/>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif></nullif>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>name</name>
+ <type>String</type>
+ <format/>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif></nullif>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field>FiveSecondsAgo</last_time_field>
+ <limit>0</limit>
+ <never_ending>N</never_ending>
+ <row_time_field>now</row_time_field>
+ <attributes/>
+ <GUI>
+ <xloc>160</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>write empty cube</name>
+ <type>CubeOutput</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <file>
+ <name>${java.io.tmpdir}/file-0050-empty.cube</name>
+ <add_to_result_filenames>N</add_to_result_filenames>
+ <do_not_open_newfile_init>N</do_not_open_newfile_init>
+ </file>
+ <attributes/>
+ <GUI>
+ <xloc>416</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0050-cube-empty-stream-refresh.hpl
b/integration-tests/transforms/0050-cube-empty-stream-refresh.hpl
new file mode 100644
index 0000000000..f5599fe1c9
--- /dev/null
+++ b/integration-tests/transforms/0050-cube-empty-stream-refresh.hpl
@@ -0,0 +1,226 @@
+<?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>0050-cube-empty-stream-refresh</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/09/15 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/15 10:00:00.000</modified_date>
+ </info>
+ <notepads>
+ <notepad>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorred>201</backgroundcolorred>
+ <bordercolorblue>90</bordercolorblue>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorred>14</bordercolorred>
+ <fontbold>N</fontbold>
+ <fontcolorblue>90</fontcolorblue>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorred>14</fontcolorred>
+ <fontitalic>N</fontitalic>
+ <fontname>Ubuntu</fontname>
+ <fontsize>11</fontsize>
+ <height>26</height>
+ <xloc>64</xloc>
+ <yloc>16</yloc>
+ <note>Cube Output receives no rows, so it has to learn the layout from
the pipeline. That made Cube Input re-open its file, which by then can be gone:
a cube refreshed in place is truncated by the output transform, a concurrent
job may have replaced it, here it is simply deleted. The layout must still end
up in the copy.</note>
+ <width>900</width>
+ </notepad>
+ </notepads>
+ <order>
+ <hop>
+ <from>read empty cube</from>
+ <to>wait until the source is gone</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>wait until the source is gone</from>
+ <to>write copy</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>one row</from>
+ <to>source file name</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>source file name</from>
+ <to>delete source cube</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>read empty cube</name>
+ <type>CubeInput</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <file>
+ <name>${java.io.tmpdir}/file-0050-empty.cube</name>
+ </file>
+ <limit>0</limit>
+ <addfilenameresult>N</addfilenameresult>
+ <attributes/>
+ <GUI>
+ <xloc>160</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>wait until the source is gone</name>
+ <type>BlockUntilTransformsFinish</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <transforms>
+ <transform>
+ <CopyNr>0</CopyNr>
+ <name>delete source cube</name>
+ </transform>
+ </transforms>
+ <attributes/>
+ <GUI>
+ <xloc>416</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>write copy</name>
+ <type>CubeOutput</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <file>
+ <name>${java.io.tmpdir}/file-0050-empty-copy.cube</name>
+ <add_to_result_filenames>N</add_to_result_filenames>
+ <do_not_open_newfile_init>N</do_not_open_newfile_init>
+ </file>
+ <attributes/>
+ <GUI>
+ <xloc>688</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>one row</name>
+ <type>RowGenerator</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field>FiveSecondsAgo</last_time_field>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field>now</row_time_field>
+ <attributes/>
+ <GUI>
+ <xloc>160</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source file name</name>
+ <type>GetVariable</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <length>-1</length>
+ <name>filename</name>
+ <precision>-1</precision>
+ <trim_type>none</trim_type>
+ <type>String</type>
+ <variable>${java.io.tmpdir}/file-0050-empty.cube</variable>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>320</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>delete source cube</name>
+ <type>ProcessFiles</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <addresultfilenames>N</addresultfilenames>
+ <createparentfolder>N</createparentfolder>
+ <operation_type>delete</operation_type>
+ <overwritetargetfile>N</overwritetargetfile>
+ <simulate>N</simulate>
+ <sourcefilenamefield>filename</sourcefilenamefield>
+ <attributes/>
+ <GUI>
+ <xloc>544</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0050-cube-empty-stream-verify.hpl
b/integration-tests/transforms/0050-cube-empty-stream-verify.hpl
new file mode 100644
index 0000000000..2731baaebe
--- /dev/null
+++ b/integration-tests/transforms/0050-cube-empty-stream-verify.hpl
@@ -0,0 +1,248 @@
+<?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>0050-cube-empty-stream-verify</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/09/15 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/15 10:00:00.000</modified_date>
+ </info>
+ <notepads>
+ <notepad>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorred>201</backgroundcolorred>
+ <bordercolorblue>90</bordercolorblue>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorred>14</bordercolorred>
+ <fontbold>N</fontbold>
+ <fontcolorblue>90</fontcolorblue>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorred>14</fontcolorred>
+ <fontitalic>N</fontitalic>
+ <fontname>Ubuntu</fontname>
+ <fontsize>11</fontsize>
+ <height>26</height>
+ <xloc>64</xloc>
+ <yloc>16</yloc>
+ <note>The copy must carry the layout of the original: id, name.</note>
+ <width>900</width>
+ </notepad>
+ </notepads>
+ <order>
+ <hop>
+ <from>read copy</from>
+ <to>one row with the layout</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>one row with the layout</from>
+ <to>layout</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>layout</from>
+ <to>concat field names</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>concat field names</from>
+ <to>not id,name</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>not id,name</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>read copy</name>
+ <type>CubeInput</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <file>
+ <name>${java.io.tmpdir}/file-0050-empty-copy.cube</name>
+ </file>
+ <limit>0</limit>
+ <addfilenameresult>N</addfilenameresult>
+ <attributes/>
+ <GUI>
+ <xloc>160</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>one row with the layout</name>
+ <type>DetectEmptyStream</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+
+ <attributes/>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>layout</name>
+ <type>TransformMetaStructure</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <includePositionField>N</includePositionField>
+ <positionFieldname>position</positionFieldname>
+ <includeFieldnameField>Y</includeFieldnameField>
+ <fieldFieldname>fieldname</fieldFieldname>
+ <includeCommentsField>N</includeCommentsField>
+ <commentsFieldname>comments</commentsFieldname>
+ <includeTypeField>N</includeTypeField>
+ <typeFieldname>type</typeFieldname>
+ <includeLengthField>N</includeLengthField>
+ <lengthFieldname>length</lengthFieldname>
+ <includePrecisionField>N</includePrecisionField>
+ <precisionFieldname>precision</precisionFieldname>
+ <includeMaskField>N</includeMaskField>
+ <maskFieldname>mask</maskFieldname>
+ <includeOriginField>N</includeOriginField>
+ <originFieldname>origin</originFieldname>
+ <outputRowcount>N</outputRowcount>
+ <rowcountField>rowcount</rowcountField>
+ <attributes/>
+ <GUI>
+ <xloc>544</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>concat field names</name>
+ <type>MemoryGroupBy</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <aggregate>fieldnames</aggregate>
+ <subject>fieldname</subject>
+ <type>CONCAT_STRING</type>
+ <valuefield>,</valuefield>
+ </field>
+ </fields>
+ <give_back_row>Y</give_back_row>
+ <group>
+ </group>
+ <attributes/>
+ <GUI>
+ <xloc>736</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>not id,name</name>
+ <type>FilterRows</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to/>
+ <send_false_to/>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>fieldnames</leftvalue>
+ <function><></function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>id,name</text>
+ <isnull>N</isnull>
+ <mask/>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>928</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description></description>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <always_log_rows>Y</always_log_rows>
+ <message>The empty cube copy does not have the layout (id, name) of the
original</message>
+ <row_threshold>0</row_threshold>
+ <attributes/>
+ <GUI>
+ <xloc>1120</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/main-0050-cube-empty-stream.hwf
b/integration-tests/transforms/main-0050-cube-empty-stream.hwf
new file mode 100644
index 0000000000..273828505e
--- /dev/null
+++ b/integration-tests/transforms/main-0050-cube-empty-stream.hwf
@@ -0,0 +1,162 @@
+<?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-0050-cube-empty-stream</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/09/15 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/15 10:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <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>0050-cube-empty-stream-generate</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0050-cube-empty-stream-generate.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <set_logfile>N</set_logfile>
+ <logfile/>
+ <logext/>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>336</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0050-cube-empty-stream-refresh</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0050-cube-empty-stream-refresh.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <set_logfile>N</set_logfile>
+ <logfile/>
+ <logext/>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>624</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0050-cube-empty-stream-verify</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0050-cube-empty-stream-verify.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <set_logfile>N</set_logfile>
+ <logfile/>
+ <logext/>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>912</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0050-cube-empty-stream-generate</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>0050-cube-empty-stream-generate</from>
+ <to>0050-cube-empty-stream-refresh</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>0050-cube-empty-stream-refresh</from>
+ <to>0050-cube-empty-stream-verify</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/plugins/transforms/cubeinput/pom.xml
b/plugins/transforms/cube/pom.xml
similarity index 92%
rename from plugins/transforms/cubeinput/pom.xml
rename to plugins/transforms/cube/pom.xml
index 63c19e9f02..96896880f7 100644
--- a/plugins/transforms/cubeinput/pom.xml
+++ b/plugins/transforms/cube/pom.xml
@@ -25,8 +25,8 @@
<version>2.20.0-SNAPSHOT</version>
</parent>
- <artifactId>hop-transform-cubeinput</artifactId>
+ <artifactId>hop-transform-cube</artifactId>
<packaging>jar</packaging>
- <name>Hop Plugins Transforms Cube Input</name>
+ <name>Hop Plugins Transforms Cube</name>
</project>
diff --git a/plugins/transforms/cubeinput/src/assembly/assembly.xml
b/plugins/transforms/cube/src/assembly/assembly.xml
similarity index 97%
rename from plugins/transforms/cubeinput/src/assembly/assembly.xml
rename to plugins/transforms/cube/src/assembly/assembly.xml
index c9b2763280..5897c0cbf2 100644
--- a/plugins/transforms/cubeinput/src/assembly/assembly.xml
+++ b/plugins/transforms/cube/src/assembly/assembly.xml
@@ -19,7 +19,7 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0
http://maven.apache.org/xsd/assembly-2.2.0.xsd">
- <id>hop-transform-cubinput</id>
+ <id>hop-transform-cube</id>
<formats>
<format>zip</format>
</formats>
diff --git
a/plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInput.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInput.java
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInput.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInput.java
diff --git
a/plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputData.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputData.java
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputData.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputData.java
diff --git
a/plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputDialog.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputDialog.java
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputDialog.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputDialog.java
diff --git
a/plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
similarity index 90%
rename from
plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
index 77fcf3210c..1741bda7ed 100644
---
a/plugins/transforms/cubeinput/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
+++
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMeta.java
@@ -94,19 +94,19 @@ public class CubeInputMeta extends
BaseTransformMeta<CubeInput, CubeInputData> {
IVariables variables,
IHopMetadataProvider metadataProvider)
throws HopTransformException {
- GZIPInputStream fis = null;
- DataInputStream dis = null;
if (file == null || file.getName() == null) {
throw new HopTransformException(
BaseMessages.getString(PKG,
"CubeInputMeta.Exception.NoFilenameSpecified"));
}
- try {
- InputStream is =
- HopVfs.getInputStream(
-
variables.resolve(file.getName().replace("${Internal.Transform.CopyNr}", "0")));
- fis = new GZIPInputStream(is);
- dis = new DataInputStream(fis);
-
+ String filename =
+
variables.resolve(file.getName().replace("${Internal.Transform.CopyNr}", "0"));
+ // The layout of a cube lives in the file itself, so we have to open it.
Resolve the name
+ // through the variables so that named VFS connections work here the same
way they do at
+ // runtime in CubeInput.init(), and close the raw stream too when the GZIP
header can't be read.
+ //
+ try (InputStream is = HopVfs.getInputStream(filename, variables);
+ GZIPInputStream fis = new GZIPInputStream(is);
+ DataInputStream dis = new DataInputStream(fis)) {
IRowMeta add = new RowMeta(dis);
for (int i = 0; i < add.size(); i++) {
add.getValueMeta(i).setOrigin(name);
@@ -114,22 +114,13 @@ public class CubeInputMeta extends
BaseTransformMeta<CubeInput, CubeInputData> {
r.mergeRowMeta(add);
} catch (HopFileException kfe) {
throw new HopTransformException(
- BaseMessages.getString(PKG,
"CubeInputMeta.Exception.UnableToReadMetaData"), kfe);
+ BaseMessages.getString(PKG,
"CubeInputMeta.Exception.UnableToReadMetaData", filename),
+ kfe);
} catch (IOException e) {
throw new HopTransformException(
- BaseMessages.getString(PKG,
"CubeInputMeta.Exception.ErrorOpeningOrReadingCubeFile"), e);
- } finally {
- try {
- if (fis != null) {
- fis.close();
- }
- if (dis != null) {
- dis.close();
- }
- } catch (IOException ioe) {
- throw new HopTransformException(
- BaseMessages.getString(PKG,
"CubeInputMeta.Exception.UnableToCloseCubeFile"), ioe);
- }
+ BaseMessages.getString(
+ PKG, "CubeInputMeta.Exception.ErrorOpeningOrReadingCubeFile",
filename),
+ e);
}
}
diff --git
a/plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
similarity index 81%
rename from
plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
index aef08c6980..59de22d5de 100644
---
a/plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
+++
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutput.java
@@ -26,6 +26,8 @@ import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopFileException;
import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.io.CountingOutputStream;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.pipeline.Pipeline;
@@ -60,11 +62,18 @@ public class CubeOutput extends
BaseTransform<CubeOutputMeta, CubeOutputData> {
if (first) { // Always run this code once, even if stream is empty
(r==null)
if (getInputRowMeta() != null) {
data.outputMeta = getInputRowMeta().clone();
- } else {
- // If the stream is empty, then row metadata probably hasn't been
received. In this case,
- // use
- // the design-time algorithm to calculate the output metadata.
- data.outputMeta = getPipelineMeta().getPrevTransformFields(this,
getTransformMeta());
+ } else if (data.outputMeta == null) {
+ // If the stream is empty, then row metadata hasn't been received. Use
the design-time
+ // layout unless init() already resolved it before opening the file.
+ data.outputMeta = lookupDesignTimeLayout();
+ if (data.outputMeta == null) {
+ logBasic(
+ BaseMessages.getString(
+ PKG,
+ "CubeOutput.Log.UnableToDetermineEmptyStreamLayout",
+ resolve(meta.getFilename())));
+ data.outputMeta = new RowMeta();
+ }
}
// If input stream is empty, but file was already opened in init(), then
@@ -122,6 +131,24 @@ public class CubeOutput extends
BaseTransform<CubeOutputMeta, CubeOutputData> {
return result;
}
+ /**
+ * The layout of the stream as the pipeline knows it at design time. Some
input transforms have to
+ * interrogate their source to answer (Cube Input reads the cube file, Table
Input runs the query
+ * metadata), so this can fail; in that case the pipeline itself reports the
problem.
+ *
+ * @return the layout, or null when it could not be determined
+ */
+ private IRowMeta lookupDesignTimeLayout() {
+ try {
+ return getPipelineMeta().getPrevTransformFields(this,
getTransformMeta());
+ } catch (HopTransformException e) {
+ logDetailed(
+ BaseMessages.getString(
+ PKG, "CubeOutput.Log.UnableToLookupDesignTimeLayout",
e.getMessage()));
+ return null;
+ }
+ }
+
private synchronized boolean writeHeaderToFile() {
try {
data.outputMeta.writeMeta(data.dos);
@@ -151,6 +178,11 @@ public class CubeOutput extends
BaseTransform<CubeOutputMeta, CubeOutputData> {
public boolean init() {
if (super.init()) {
if (!meta.isDoNotOpenNewFileInit()) {
+ // Resolve the layout of the incoming stream before opening the output
file. Opening
+ // truncates it, and when the pipeline refreshes a cube in place the
Cube Input upstream can
+ // no longer read that file to tell us its layout at the end of an
empty run.
+ //
+ data.outputMeta = lookupDesignTimeLayout();
try {
prepareFile();
data.oneFileOpened = true;
diff --git
a/plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputData.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputData.java
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputData.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputData.java
diff --git
a/plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputDialog.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputDialog.java
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputDialog.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputDialog.java
diff --git
a/plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMeta.java
b/plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMeta.java
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMeta.java
rename to
plugins/transforms/cube/src/main/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMeta.java
diff --git a/plugins/transforms/cubeinput/src/main/resources/cubeinput.svg
b/plugins/transforms/cube/src/main/resources/cubeinput.svg
similarity index 100%
rename from plugins/transforms/cubeinput/src/main/resources/cubeinput.svg
rename to plugins/transforms/cube/src/main/resources/cubeinput.svg
diff --git a/plugins/transforms/cubeoutput/src/main/resources/cubeoutput.svg
b/plugins/transforms/cube/src/main/resources/cubeoutput.svg
similarity index 100%
rename from plugins/transforms/cubeoutput/src/main/resources/cubeoutput.svg
rename to plugins/transforms/cube/src/main/resources/cubeoutput.svg
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_de_DE.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_de_DE.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_de_DE.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_de_DE.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
similarity index 97%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
index acb694ea61..d6b9d056d8 100644
---
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
+++
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_en_US.properties
@@ -31,9 +31,9 @@ CubeInputDialog.Limit.Label=Limit size
CubeInputDialog.Shell.Title=De-serialize from file
CubeInputDialog.TransformName.Label=Transform name
CubeInputMeta.CheckResult.FileSpecificationsNotChecked=File specifications are
not checked.
-CubeInputMeta.Exception.ErrorOpeningOrReadingCubeFile=Error opening/reading
cube file
+CubeInputMeta.Exception.ErrorOpeningOrReadingCubeFile=Error opening/reading
cube file [{0}]
CubeInputMeta.Exception.NoFilenameSpecified=No cube file name has been
specified for this transform
CubeInputMeta.Exception.UnableToCloseCubeFile=Unable to close cube file
CubeInputMeta.Exception.UnableToLoadTransformMeta=Unable to load transform
info from XML
-CubeInputMeta.Exception.UnableToReadMetaData=Unable to read metadata from cube
file
+CubeInputMeta.Exception.UnableToReadMetaData=Unable to read metadata from cube
file [{0}]
CubeInputMeta.keyword=cube,deserialize,read,binary,serialized
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_AR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_AR.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_AR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_AR.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_ES.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_ES.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_ES.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_es_ES.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_fr_FR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_fr_FR.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_fr_FR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_fr_FR.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_it_IT.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_it_IT.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_it_IT.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_it_IT.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ja_JP.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ja_JP.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ja_JP.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ja_JP.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ko_KR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ko_KR.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ko_KR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_ko_KR.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_nl_NL.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_nl_NL.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_nl_NL.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_nl_NL.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_no_NO.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_no_NO.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_no_NO.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_no_NO.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pl_PL.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pl_PL.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pl_PL.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pl_PL.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_BR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_BR.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_BR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_BR.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_PT.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_PT.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_PT.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_pt_PT.properties
diff --git
a/plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_zh_CN.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_zh_CN.properties
similarity index 100%
rename from
plugins/transforms/cubeinput/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_zh_CN.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeinput/messages/messages_zh_CN.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_de_DE.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_de_DE.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_de_DE.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_de_DE.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
similarity index 88%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
index 3c4eb1e45f..7fdc7fe97b 100644
---
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
+++
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_en_US.properties
@@ -40,3 +40,5 @@ CubeOutputMeta.CheckResult.FileSpecificationsNotChecked=File
specifications are
CubeOutputMeta.CheckResult.ReceivingFields=Transform is connected to previous
one, receiving {0} fields
CubeOutputMeta.Exception.UnableToLoadTransformMeta=Unable to load transform
info from XML
CubeOutputMeta.keyword=serialize,cube,binary,write,file
+CubeOutput.Log.UnableToDetermineEmptyStreamLayout=WARNING\: no rows were
received and the layout of the input stream could not be determined, writing an
empty cube file without fields to [{0}]
+CubeOutput.Log.UnableToLookupDesignTimeLayout=Could not determine the layout
of the input stream from the pipeline\: {0}
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_AR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_AR.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_AR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_AR.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_ES.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_ES.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_ES.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_es_ES.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_fr_FR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_fr_FR.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_fr_FR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_fr_FR.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_it_IT.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_it_IT.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_it_IT.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_it_IT.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ja_JP.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ja_JP.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ja_JP.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ja_JP.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ko_KR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ko_KR.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ko_KR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_ko_KR.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_nl_NL.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_nl_NL.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_nl_NL.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_nl_NL.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_no_NO.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_no_NO.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_no_NO.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_no_NO.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pl_PL.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pl_PL.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pl_PL.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pl_PL.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_BR.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_BR.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_BR.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_BR.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_PT.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_PT.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_PT.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_pt_PT.properties
diff --git
a/plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_zh_CN.properties
b/plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_zh_CN.properties
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_zh_CN.properties
rename to
plugins/transforms/cube/src/main/resources/org/apache/hop/pipeline/transforms/cubeoutput/messages/messages_zh_CN.properties
diff --git a/plugins/transforms/cubeinput/src/main/resources/version.xml
b/plugins/transforms/cube/src/main/resources/version.xml
similarity index 100%
rename from plugins/transforms/cubeinput/src/main/resources/version.xml
rename to plugins/transforms/cube/src/main/resources/version.xml
diff --git
a/plugins/transforms/cubeinput/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
similarity index 65%
rename from
plugins/transforms/cubeinput/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
rename to
plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
index 361da84d8e..405f04d87d 100644
---
a/plugins/transforms/cubeinput/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
+++
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeinput/CubeInputMetaTest.java
@@ -21,14 +21,20 @@ import static
org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.EOFException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.variables.Variables;
import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
class CubeInputMetaTest {
+ @TempDir Path tempDir;
+
@Test
void testRoundTrip() throws Exception {
CubeInputMeta meta =
@@ -52,4 +58,25 @@ class CubeInputMetaTest {
() -> meta.getFields(new RowMeta(), "cube input", null, null, new
Variables(), null));
assertTrue(e.getMessage().contains("cube file name"), e.getMessage());
}
+
+ /**
+ * The layout of a cube lives in the file, so getFields() has to open it.
That also happens at
+ * runtime, when a downstream transform that received no rows falls back to
the design-time
+ * layout: if the file is empty or half-written by then (a cube refreshed in
place, a concurrent
+ * writer), we fail with a message that names the file instead of a bare
EOFException.
+ */
+ @Test
+ void getFieldsOnTruncatedCubeFileReportsTheFile() throws Exception {
+ Path cubeFile = Files.createFile(tempDir.resolve("truncated.cube"));
+ CubeInputMeta meta = new CubeInputMeta();
+ meta.setDefault();
+ meta.getFile().setName(cubeFile.toString());
+
+ HopTransformException e =
+ assertThrows(
+ HopTransformException.class,
+ () -> meta.getFields(new RowMeta(), "cube input", null, null, new
Variables(), null));
+ assertTrue(e.getMessage().contains(cubeFile.toString()), e.getMessage());
+ assertTrue(e.getCause() instanceof EOFException,
String.valueOf(e.getCause()));
+ }
}
diff --git
a/plugins/transforms/cubeoutput/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMetaTest.java
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMetaTest.java
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMetaTest.java
rename to
plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputMetaTest.java
diff --git
a/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputTest.java
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputTest.java
new file mode 100644
index 0000000000..b0eea80777
--- /dev/null
+++
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeOutputTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.cubeoutput;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.GZIPInputStream;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopEofException;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopTransformException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+
+class CubeOutputTest {
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ @TempDir Path tempDir;
+
+ private TransformMockHelper<CubeOutputMeta, CubeOutputData> helper;
+
+ @BeforeAll
+ static void setUpBeforeClass() throws HopException {
+ HopEnvironment.init();
+ }
+
+ @BeforeEach
+ void setUp() {
+ helper = new TransformMockHelper<>("Cube output", CubeOutputMeta.class,
CubeOutputData.class);
+ when(helper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+ .thenReturn(helper.iLogChannel);
+ when(helper.pipeline.isRunning()).thenReturn(true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ helper.cleanUp();
+ }
+
+ /**
+ * When no rows arrive, the output layout is derived from the pipeline
graph. That walk can fail
+ * because an upstream input transform has to re-open its source to answer
(a Cube Input whose
+ * file is being rewritten in place by this very transform, for example).
The run used to end in
+ * an error with a cube file that had a GZIP header but no layout; now it
writes a valid, empty
+ * cube without fields.
+ */
+ @Test
+ void emptyStreamWithUnknownLayoutWritesAnEmptyCube() throws Exception {
+ Path cubeFile = tempDir.resolve("empty.cube");
+ CubeOutput cubeOutput = createCubeOutput(cubeFile);
+ when(helper.pipelineMeta.getPrevTransformFields(any(),
any(TransformMeta.class)))
+ .thenThrow(
+ new HopTransformException("Error opening/reading cube file", new
EOFException()));
+ doReturn(null).when(cubeOutput).getRow();
+
+ assertTrue(cubeOutput.init());
+ assertFalse(cubeOutput.processRow());
+ cubeOutput.dispose();
+
+ assertEquals(0, cubeOutput.getErrors());
+ assertEquals(0, readCubeLayout(cubeFile).size());
+ }
+
+ @Test
+ void emptyStreamWithKnownLayoutWritesTheLayout() throws Exception {
+ Path cubeFile = tempDir.resolve("empty.cube");
+ CubeOutput cubeOutput = createCubeOutput(cubeFile);
+ IRowMeta layout = new RowMeta();
+ layout.addValueMeta(new
org.apache.hop.core.row.value.ValueMetaString("name"));
+ when(helper.pipelineMeta.getPrevTransformFields(any(),
any(TransformMeta.class)))
+ .thenReturn(layout);
+ doReturn(null).when(cubeOutput).getRow();
+
+ assertTrue(cubeOutput.init());
+ assertFalse(cubeOutput.processRow());
+ cubeOutput.dispose();
+
+ assertEquals(0, cubeOutput.getErrors());
+ IRowMeta written = readCubeLayout(cubeFile);
+ assertEquals(1, written.size());
+ assertEquals("name", written.getValueMeta(0).getName());
+ }
+
+ private CubeOutput createCubeOutput(Path cubeFile) {
+ CubeOutputMeta meta = new CubeOutputMeta();
+ meta.setDefault();
+ meta.setFilename(cubeFile.toString());
+ when(helper.transformMeta.getTransform()).thenReturn(meta);
+ CubeOutput cubeOutput =
+ new CubeOutput(
+ helper.transformMeta,
+ meta,
+ new CubeOutputData(),
+ 0,
+ helper.pipelineMeta,
+ helper.pipeline);
+ return spy(cubeOutput);
+ }
+
+ /** Reads the file the way Cube Input does: header, then rows until the
end-of-file marker. */
+ private static IRowMeta readCubeLayout(Path cubeFile) throws Exception {
+ try (InputStream is = Files.newInputStream(cubeFile);
+ DataInputStream dis = new DataInputStream(new GZIPInputStream(is))) {
+ IRowMeta rowMeta = new RowMeta(dis);
+ assertThrows(HopEofException.class, () -> rowMeta.readData(dis));
+ return rowMeta;
+ }
+ }
+}
diff --git
a/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeRoundTripTest.java
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeRoundTripTest.java
new file mode 100644
index 0000000000..298e9b0e89
--- /dev/null
+++
b/plugins/transforms/cube/src/test/java/org/apache/hop/pipeline/transforms/cubeoutput/CubeRoundTripTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.cubeoutput;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.logging.LoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.cubeinput.CubeInputMeta;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/** Runs a real Cube Input → Cube Output pipeline. */
+class CubeRoundTripTest {
+
+ @TempDir Path tempDir;
+
+ @BeforeAll
+ static void setUpBeforeClass() throws Exception {
+ HopEnvironment.init();
+ }
+
+ /**
+ * An empty cube still has a layout. Reading it and writing it back (here to
another file, but
+ * refreshing the same file in place is the case that used to fail) must
produce a cube with that
+ * same layout and no rows, without the output transform having to re-open
the input file at the
+ * end of the run to find out what the layout was.
+ */
+ @Test
+ void emptyCubeKeepsItsLayoutThroughAPipeline() throws Exception {
+ IRowMeta layout = new RowMeta();
+ layout.addValueMeta(new ValueMetaString("name"));
+ layout.addValueMeta(new ValueMetaInteger("id"));
+ Path source = tempDir.resolve("source.cube");
+ writeEmptyCube(source, layout);
+ Path target = tempDir.resolve("target.cube");
+
+ CubeInputMeta inputMeta = new CubeInputMeta();
+ inputMeta.setDefault();
+ inputMeta.getFile().setName(source.toString());
+ CubeOutputMeta outputMeta = new CubeOutputMeta();
+ outputMeta.setDefault();
+ outputMeta.setFilename(target.toString());
+
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.setName("cube round trip");
+ TransformMeta input = new TransformMeta("Cube input", inputMeta);
+ TransformMeta output = new TransformMeta("Cube output", outputMeta);
+ pipelineMeta.addTransform(input);
+ pipelineMeta.addTransform(output);
+ pipelineMeta.addPipelineHop(new PipelineHopMeta(input, output));
+
+ // Truncate the source before the output transform gets a chance to ask
for its layout again:
+ // this is what an in-place refresh (same file for input and output) does
in init().
+ //
+ LocalPipelineEngine pipeline =
+ new LocalPipelineEngine(pipelineMeta, new Variables(), new
LoggingObject("test"));
+ pipeline.prepareExecution();
+ Files.write(source, new byte[0]);
+ pipeline.startThreads();
+ pipeline.waitUntilFinished();
+
+ assertEquals(0, pipeline.getErrors(), "the run reported errors");
+ IRowMeta written = readCubeLayout(target);
+ assertEquals(layout.getFieldNames().length, written.size());
+ assertEquals("name", written.getValueMeta(0).getName());
+ assertEquals("id", written.getValueMeta(1).getName());
+ }
+
+ private static void writeEmptyCube(Path file, IRowMeta layout) throws
Exception {
+ try (OutputStream os = Files.newOutputStream(file);
+ DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)))
{
+ layout.writeMeta(dos);
+ }
+ }
+
+ private static IRowMeta readCubeLayout(Path file) throws Exception {
+ try (InputStream is = Files.newInputStream(file);
+ DataInputStream dis = new DataInputStream(new GZIPInputStream(is))) {
+ return new RowMeta(dis);
+ }
+ }
+}
diff --git
a/plugins/transforms/cubeinput/src/test/resources/de-serialize-transform.xml
b/plugins/transforms/cube/src/test/resources/de-serialize-transform.xml
similarity index 100%
rename from
plugins/transforms/cubeinput/src/test/resources/de-serialize-transform.xml
rename to plugins/transforms/cube/src/test/resources/de-serialize-transform.xml
diff --git
a/plugins/transforms/cubeoutput/src/test/resources/serialize-transform.xml
b/plugins/transforms/cube/src/test/resources/serialize-transform.xml
similarity index 100%
rename from
plugins/transforms/cubeoutput/src/test/resources/serialize-transform.xml
rename to plugins/transforms/cube/src/test/resources/serialize-transform.xml
diff --git a/plugins/transforms/cubeoutput/pom.xml
b/plugins/transforms/cubeoutput/pom.xml
deleted file mode 100644
index cbf79e25f0..0000000000
--- a/plugins/transforms/cubeoutput/pom.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
- ~
- -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>org.apache.hop</groupId>
- <artifactId>hop-plugins-transforms</artifactId>
- <version>2.20.0-SNAPSHOT</version>
- </parent>
-
- <artifactId>hop-transform-cubeoutput</artifactId>
- <packaging>jar</packaging>
- <name>Hop Plugins Transforms Cube Output</name>
-
-</project>
diff --git a/plugins/transforms/cubeoutput/src/assembly/assembly.xml
b/plugins/transforms/cubeoutput/src/assembly/assembly.xml
deleted file mode 100644
index 86c00c3080..0000000000
--- a/plugins/transforms/cubeoutput/src/assembly/assembly.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<!--
- ~ 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.
- ~
- -->
-
-<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0
http://maven.apache.org/xsd/assembly-2.2.0.xsd">
- <id>hop-transform-cubeoutput</id>
- <formats>
- <format>zip</format>
- </formats>
- <baseDirectory>.</baseDirectory>
- <files>
- <file>
- <source>${project.basedir}/src/main/resources/version.xml</source>
- <outputDirectory>${hop.plugin.libdir}</outputDirectory>
- <filtered>true</filtered>
- </file>
- </files>
-
- <fileSets>
- <fileSet>
- <directory>${project.basedir}/src/main/samples</directory>
- <outputDirectory>config/projects/samples/</outputDirectory>
- </fileSet>
- </fileSets>
-
-
- <componentDescriptors>
-
<componentDescriptor>${maven.multiModuleProjectDirectory}/assemblies/shared/hop-plugin-libs.xml</componentDescriptor>
- </componentDescriptors>
-</assembly>
\ No newline at end of file
diff --git a/plugins/transforms/cubeoutput/src/main/resources/version.xml
b/plugins/transforms/cubeoutput/src/main/resources/version.xml
deleted file mode 100644
index 6be576acae..0000000000
--- a/plugins/transforms/cubeoutput/src/main/resources/version.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
- ~
- -->
-
-<version>${project.version}</version>
\ No newline at end of file
diff --git a/plugins/transforms/pom.xml b/plugins/transforms/pom.xml
index ebb3fa9eab..240b5e594d 100644
--- a/plugins/transforms/pom.xml
+++ b/plugins/transforms/pom.xml
@@ -47,8 +47,7 @@
<module>concatfields</module>
<module>constant</module>
<module>creditcardvalidator</module>
- <module>cubeinput</module>
- <module>cubeoutput</module>
+ <module>cube</module>
<module>databasejoin</module>
<module>databaselookup</module>
<module>databasevaluevalidation</module>