diff --git a/.gitignore b/.gitignore
index 64433a06aaa..28e2f00e632 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,3 +83,6 @@ screenshots/
# Docker script leaving audit data
docker/local-data/
/docker/marketplace-nexus/.env
+
+# Jenkins-style local deploy output (verify-ci-snapshot-zips.sh)
+local-snapshots-dir/
diff --git a/Jenkinsfile b/Jenkinsfile
index d604d787c08..406bc894975 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -113,12 +113,23 @@ pipeline {
}
}
}
+ stage('Assembly size check') {
+ when {
+ anyOf { changeset pattern: "^(?!docs).*^(?!integration-tests).*" , comparator: "REGEXP" ; equals expected: true, actual: params.FORCE_BUILD }
+ }
+ steps {
+ // ASF Artifactory rejects packages over ~850MB; fail early with margin.
+ sh "./tools/check-assembly-size.sh"
+ }
+ }
stage('Unzip Apache Hop'){
when {
anyOf { changeset pattern: "^(?!docs).*^(?!integration-tests).*" , comparator: "REGEXP" ; equals expected: true, actual: params.FORCE_BUILD }
}
steps{
sh "unzip ./assemblies/client/target/hop-client-*.zip -d ./assemblies/client/target/"
+ // Optional Wave 1 plugins are marketplace-only; install into the extracted client for IT/Docker
+ sh "./tools/install-wave1-plugins.sh ./assemblies/client/target/hop"
sh "unzip ./assemblies/web/target/hop.war -d ./assemblies/web/target/webapp"
}
}
diff --git a/assemblies/client/pom.xml b/assemblies/client/pom.xml
index c7e1dd1c21e..155ae1ef27d 100644
--- a/assemblies/client/pom.xml
+++ b/assemblies/client/pom.xml
@@ -61,18 +61,17 @@
${project.version}zip
+
org.apache.hophop-engines-beam${project.version}
+ libszip
-
- org.apache.hop
- hop-engines-spark
- ${project.version}
- zip
-
+
org.apache.hophop-ui
@@ -144,4 +143,25 @@
+
+
+
+ beam
+
+
+ org.apache.hop
+ hop-engines-beam
+ ${project.version}
+ zip
+
+
+
+
+
diff --git a/assemblies/client/src/assembly/assembly.xml b/assemblies/client/src/assembly/assembly.xml
index d25f356db3c..58b15cad028 100644
--- a/assemblies/client/src/assembly/assembly.xml
+++ b/assemblies/client/src/assembly/assembly.xml
@@ -40,6 +40,9 @@
org.apache.hop:hop-core:ziporg.apache.hop:hop-engine:ziporg.apache.hop:hop-ui:zip
+
+ org.apache.hop:hop-engines-beam:zip:libs
+
org.apache.hop:hop-engines-beam:ziporg.apache.hop:hop-ui-rcp:zip
diff --git a/assemblies/plugins/pom.xml b/assemblies/plugins/pom.xml
index 30d0e6d0f52..36486762b9c 100644
--- a/assemblies/plugins/pom.xml
+++ b/assemblies/plugins/pom.xml
@@ -618,12 +618,7 @@
${project.version}zip
-
- org.apache.hop
- hop-engines-spark
- ${project.version}
- zip
-
+
org.apache.hophop-misc-async
@@ -636,27 +631,18 @@
${project.version}zip
-
- org.apache.hop
- hop-misc-documentation
- ${project.version}
- zip
-
+
org.apache.hophop-misc-git${project.version}zip
+
+
org.apache.hop
- hop-misc-import
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-misc-mail
+ hop-misc-marketplace${project.version}zip
@@ -672,18 +658,14 @@
${project.version}zip
-
- org.apache.hop
- hop-misc-py4j
- 2.19.0-SNAPSHOT
- zip
-
+
org.apache.hophop-misc-reflection${project.version}zip
+
org.apache.hophop-misc-rest
@@ -708,97 +690,20 @@
2.19.0-SNAPSHOTzip
-
-
- org.apache.hop
- hop-tech-arrow
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-avro
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-aws
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-azure
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-cassandra
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-databricks
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-dropbox
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-elastic
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-google
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-minio
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-mongodb
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-neo4j
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-opensearch
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-parquet
- ${project.version}
- zip
-
-
- org.apache.hop
- hop-tech-salesforce
- ${project.version}
- zip
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
org.apache.hophop-tech-vault
@@ -1009,24 +914,14 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-drools
- ${project.version}
- zip
-
+
org.apache.hophop-transform-dynamicsqlrow${project.version}zip
-
- org.apache.hop
- hop-transform-edi2xml
- ${project.version}
- zip
-
+
org.apache.hophop-transform-excel
@@ -1051,12 +946,7 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-fake
- ${project.version}
- zip
-
+
org.apache.hophop-transform-fieldschangesequence
@@ -1417,12 +1307,7 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-script
- ${project.version}
- zip
-
+
org.apache.hophop-transform-selectvalues
@@ -1507,12 +1392,7 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-stanfordnlp
- ${project.version}
- zip
-
+
org.apache.hophop-transform-streamlookup
@@ -1591,12 +1471,7 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-tika
- ${project.version}
- zip
-
+
org.apache.hophop-transform-tokenreplacement
@@ -1633,12 +1508,7 @@
${project.version}zip
-
- org.apache.hop
- hop-transform-vcard
- ${project.version}
- zip
-
+
org.apache.hophop-transform-webserviceavailable
diff --git a/assemblies/plugins/src/assembly/assembly.xml b/assemblies/plugins/src/assembly/assembly.xml
index ed458154b5b..199b54fa4c5 100644
--- a/assemblies/plugins/src/assembly/assembly.xml
+++ b/assemblies/plugins/src/assembly/assembly.xml
@@ -35,24 +35,35 @@
false
+
databases**/*.zip
+
+ **/target/**
+ transforms**/*.zip
+
+ **/target/**
+ actions**/*.zip
+
+ **/target/**
+
\ No newline at end of file
diff --git a/assemblies/static/src/main/resources/hop b/assemblies/static/src/main/resources/hop
index 567efb609fe..ed52c6936f3 100755
--- a/assemblies/static/src/main/resources/hop
+++ b/assemblies/static/src/main/resources/hop
@@ -86,16 +86,16 @@ Linux)
export GDK_BACKEND=x11
fi
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -108,6 +108,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
+
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.hop.Hop "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-conf.bat b/assemblies/static/src/main/resources/hop-conf.bat
index e33f891e3ec..12726246c83 100644
--- a/assemblies/static/src/main/resources/hop-conf.bat
+++ b/assemblies/static/src/main/resources/hop-conf.bat
@@ -1,105 +1,113 @@
-@echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Conf
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-conf.bat]===================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
-echo.
-echo ===[Starting HopConfig]=========================================================
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
+@echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Conf
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-conf.bat]===================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
+echo.
+echo ===[Starting HopConfig]=========================================================
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
diff --git a/assemblies/static/src/main/resources/hop-conf.sh b/assemblies/static/src/main/resources/hop-conf.sh
index b279b2ad933..67dd91794e5 100755
--- a/assemblies/static/src/main/resources/hop-conf.sh
+++ b/assemblies/static/src/main/resources/hop-conf.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -103,6 +103,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.config.HopConfig "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-encrypt.bat b/assemblies/static/src/main/resources/hop-encrypt.bat
index a5877064c0d..66b37dea1e4 100644
--- a/assemblies/static/src/main/resources/hop-encrypt.bat
+++ b/assemblies/static/src/main/resources/hop-encrypt.bat
@@ -1,109 +1,117 @@
-echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-echo off
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx64m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=GUI
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-
-echo ===[Environment Settings - hop-encrypt.bat]====================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.core.encryption.Encr %_cmdline%
-echo.
-echo ===[Starting HopEncrypt]=========================================================
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.encryption.HopEncrypt %_cmdline%
-@echo off
+echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+echo off
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx64m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=GUI
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+
+echo ===[Environment Settings - hop-encrypt.bat]====================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.core.encryption.Encr %_cmdline%
+echo.
+echo ===[Starting HopEncrypt]=========================================================
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.encryption.HopEncrypt %_cmdline%
+@echo off
:End
\ No newline at end of file
diff --git a/assemblies/static/src/main/resources/hop-encrypt.sh b/assemblies/static/src/main/resources/hop-encrypt.sh
index b89a53adedf..365b2e72d65 100755
--- a/assemblies/static/src/main/resources/hop-encrypt.sh
+++ b/assemblies/static/src/main/resources/hop-encrypt.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -103,6 +103,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -classpath "${CLASSPATH}" org.apache.hop.encryption.HopEncrypt "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-gui-nolog.bat b/assemblies/static/src/main/resources/hop-gui-nolog.bat
index b830fecc0f5..042384cd8ef 100644
--- a/assemblies/static/src/main/resources/hop-gui-nolog.bat
+++ b/assemblies/static/src/main/resources/hop-gui-nolog.bat
@@ -24,8 +24,16 @@ cd /D %~dp0
REM Option to change the Characterset of the Windows Shell to show foreign caracters
if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\swt\win64\*
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\swt\win64\*
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
set _temphelp=0
if [%1]==[help] set _temphelp=1
diff --git a/assemblies/static/src/main/resources/hop-gui.bat b/assemblies/static/src/main/resources/hop-gui.bat
index e6ef8c93e7f..7fb165def5c 100755
--- a/assemblies/static/src/main/resources/hop-gui.bat
+++ b/assemblies/static/src/main/resources/hop-gui.bat
@@ -1,125 +1,133 @@
-@echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-REM Option to change the Characterset of the Windows Shell to show foreign caracters
-if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-set _temphelp=0
-if [%1]==[help] set _temphelp=1
-if [%1]==[Help] set _temphelp=1
-if %_temphelp%==1 (GOTO Help) ELSE (GOTO NormalStart)
-
-:Help
-echo ===[Hop Help - hop-gui.bat]===============================================
-echo Starts Hop GUI and keeps this console window open so you can see log output.
-echo To start without a console window, use hop-gui-nolog.bat instead.
-echo.
-echo Normally, no parameters are required to start Hop. There is a debug mode
-echo that you can start by passing in DEBUG as the first parameter after hop-gui.bat
-echo.
-echo Example:
-echo hop-gui.bat DEBUG
-echo
-echo The debug mode opens port 5005 locally when Hop starts allowing you to attaching
-echo a debugger from your favorite Java IDE tool and step code.
-echo ==========================================================================
-GOTO End
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5005
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger + turning on GUI debug logging
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -DHOP_LOG_LEVEL=Debug)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=GUI
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-gui.bat]===================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.ui.hopgui.HopGui
-echo.
-echo ===[Starting Hop]=========================================================
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Dswt.autoScale=false -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.ui.hopgui.HopGui
-if ERRORLEVEL 1 (pause)
-
-:End
-endlocal
+@echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+REM Option to change the Characterset of the Windows Shell to show foreign caracters
+if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+set _temphelp=0
+if [%1]==[help] set _temphelp=1
+if [%1]==[Help] set _temphelp=1
+if %_temphelp%==1 (GOTO Help) ELSE (GOTO NormalStart)
+
+:Help
+echo ===[Hop Help - hop-gui.bat]===============================================
+echo Starts Hop GUI and keeps this console window open so you can see log output.
+echo To start without a console window, use hop-gui-nolog.bat instead.
+echo.
+echo Normally, no parameters are required to start Hop. There is a debug mode
+echo that you can start by passing in DEBUG as the first parameter after hop-gui.bat
+echo.
+echo Example:
+echo hop-gui.bat DEBUG
+echo
+echo The debug mode opens port 5005 locally when Hop starts allowing you to attaching
+echo a debugger from your favorite Java IDE tool and step code.
+echo ==========================================================================
+GOTO End
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5005
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger + turning on GUI debug logging
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -DHOP_LOG_LEVEL=Debug)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=GUI
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-gui.bat]===================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.ui.hopgui.HopGui
+echo.
+echo ===[Starting Hop]=========================================================
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Dswt.autoScale=false -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.ui.hopgui.HopGui
+if ERRORLEVEL 1 (pause)
+
+:End
+endlocal
diff --git a/assemblies/static/src/main/resources/hop-gui.sh b/assemblies/static/src/main/resources/hop-gui.sh
index ec2dbc546f1..188ec8af383 100755
--- a/assemblies/static/src/main/resources/hop-gui.sh
+++ b/assemblies/static/src/main/resources/hop-gui.sh
@@ -111,7 +111,7 @@ Darwin)
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
esac
-CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/$os_path/$arch_path/*"
+CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/$os_path/$arch_path/*"
@@ -122,6 +122,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.ui.hopgui.HopGui "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-import.bat b/assemblies/static/src/main/resources/hop-import.bat
index 582dcdb396f..5a4b91e88c8 100644
--- a/assemblies/static/src/main/resources/hop-import.bat
+++ b/assemblies/static/src/main/resources/hop-import.bat
@@ -1,108 +1,116 @@
-echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-echo off
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Import
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-import.bat]===================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
-echo.
-echo ===[Starting HopConfig]=========================================================
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.imp.HopImport %_cmdline%
-@echo off
-:End
+echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+echo off
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Import
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-import.bat]===================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
+echo.
+echo ===[Starting HopConfig]=========================================================
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.imp.HopImport %_cmdline%
+@echo off
+:End
diff --git a/assemblies/static/src/main/resources/hop-import.sh b/assemblies/static/src/main/resources/hop-import.sh
index 5669e164e5e..5ee373c1ae9 100755
--- a/assemblies/static/src/main/resources/hop-import.sh
+++ b/assemblies/static/src/main/resources/hop-import.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -103,6 +103,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.imp.HopImport "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-run.bat b/assemblies/static/src/main/resources/hop-run.bat
index 424a2737daa..9c4e2152278 100755
--- a/assemblies/static/src/main/resources/hop-run.bat
+++ b/assemblies/static/src/main/resources/hop-run.bat
@@ -1,128 +1,136 @@
-@echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-REM Option to change the Characterset of the Windows Shell to show foreign caracters
-if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-set _temphelp=0
-if [%1]==[help] set _temphelp=1
-if [%1]==[Help] set _temphelp=1
-if %_temphelp%==1 (GOTO Help) ELSE (GOTO NormalStart)
-
-:Help
-echo ===[HopRun - hop-run.bat]=================================================
-echo.
-echo Example:
-echo hop-run.bat --file=C:\Users\usbra\Desktop\converted_financial_metrics\update_dashboard_financials.hwf --environment=converted_financial_metrics --project=converted_financial_metrics --runconfig=local
-echo
-echo The above gives a full path to a file. It assumes a project has been created for that file called 'converted_financial_metrics'
-echo The project also has an environment called 'converted_financial_metrics', that was created when creating the project.
-echo Finally, 'local' is the name of the local configuration json file present in the project's metadata folder. There is one for workflows and another for pipelines.
-echo ==========================================================================
-GOTO End
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA="java"
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS="-Xmx2048m"
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5005
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Run
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-run.bat]===================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo.
-echo Consolidated parameters to pass to HopRun are
-echo %_cmdline%%
-echo.
-echo Command to start HopRun will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.run.HopRun %_cmdline%%
-echo.
-echo ===[Starting HopRun]=========================================================
+@echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+REM Option to change the Characterset of the Windows Shell to show foreign caracters
+if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+set _temphelp=0
+if [%1]==[help] set _temphelp=1
+if [%1]==[Help] set _temphelp=1
+if %_temphelp%==1 (GOTO Help) ELSE (GOTO NormalStart)
+
+:Help
+echo ===[HopRun - hop-run.bat]=================================================
+echo.
+echo Example:
+echo hop-run.bat --file=C:\Users\usbra\Desktop\converted_financial_metrics\update_dashboard_financials.hwf --environment=converted_financial_metrics --project=converted_financial_metrics --runconfig=local
+echo
+echo The above gives a full path to a file. It assumes a project has been created for that file called 'converted_financial_metrics'
+echo The project also has an environment called 'converted_financial_metrics', that was created when creating the project.
+echo Finally, 'local' is the name of the local configuration json file present in the project's metadata folder. There is one for workflows and another for pipelines.
+echo ==========================================================================
+GOTO End
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA="java"
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS="-Xmx2048m"
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5005
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Run
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-run.bat]===================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo.
+echo Consolidated parameters to pass to HopRun are
+echo %_cmdline%%
+echo.
+echo Command to start HopRun will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.run.HopRun %_cmdline%%
+echo.
+echo ===[Starting HopRun]=========================================================
%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.run.HopRun %_cmdline%%
\ No newline at end of file
diff --git a/assemblies/static/src/main/resources/hop-run.sh b/assemblies/static/src/main/resources/hop-run.sh
index f25c19bac80..83c03b7f5f4 100755
--- a/assemblies/static/src/main/resources/hop-run.sh
+++ b/assemblies/static/src/main/resources/hop-run.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -103,6 +103,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.run.HopRun "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-search.bat b/assemblies/static/src/main/resources/hop-search.bat
index be6d8e37a5d..35571bb4640 100644
--- a/assemblies/static/src/main/resources/hop-search.bat
+++ b/assemblies/static/src/main/resources/hop-search.bat
@@ -1,105 +1,113 @@
-@echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Search
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-search.bat]===================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
-echo.
-echo ===[Starting HopConfig]=========================================================
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.search.HopSearch %_cmdline%
+@echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Search
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-search.bat]===================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.config.HopConfig %_cmdline%
+echo.
+echo ===[Starting HopConfig]=========================================================
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.search.HopSearch %_cmdline%
diff --git a/assemblies/static/src/main/resources/hop-search.sh b/assemblies/static/src/main/resources/hop-search.sh
index 6bba102dea0..607f53261e0 100755
--- a/assemblies/static/src/main/resources/hop-search.sh
+++ b/assemblies/static/src/main/resources/hop-search.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -103,6 +103,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.search.HopSearch "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop-server.bat b/assemblies/static/src/main/resources/hop-server.bat
index 40227e5c65e..d47d761bf04 100644
--- a/assemblies/static/src/main/resources/hop-server.bat
+++ b/assemblies/static/src/main/resources/hop-server.bat
@@ -1,109 +1,117 @@
-@echo off
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal
-
-REM switch to script directory
-cd /D %~dp0
-
-REM Option to change the Characterset of the Windows Shell to show foreign caracters
-if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA=java
-)
-
-REM # Settings for all OSses
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
-
-REM
-REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
-REM to allow attaching a debugger to step code.
-if [%1]==[DEBUG] (
-REM # optional line for attaching a debugger
-set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
-
-REM Pass HOP variables if they're set.
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Server
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-echo ===[Environment Settings - hop-server.bat]====================================
-echo.
-echo Java identified as %_HOP_JAVA%
-echo.
-echo HOP_OPTIONS=%HOP_OPTIONS%
-echo.
-echo.
-rem ===[Collect command line arguments...]======================================
-if [%1]==[DEBUG] (
-FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
-GOTO Run
-)
-set _cmdline=%*
-
-:Run
-echo Command to start Hop will be:
-echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.www.HopServer %_cmdline%
-echo.
-echo ===[Starting HopServer]=========================================================
-
+@echo off
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal
+
+REM switch to script directory
+cd /D %~dp0
+
+REM Option to change the Characterset of the Windows Shell to show foreign caracters
+if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA=java
+)
+
+REM # Settings for all OSses
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS=-Xmx2048m
+
+REM
+REM If the user passes in DEBUG as the first parameter, it starts Hop in debugger mode and opens port 5009
+REM to allow attaching a debugger to step code.
+if [%1]==[DEBUG] (
+REM # optional line for attaching a debugger
+set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5009)
+
+REM Pass HOP variables if they're set.
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Server
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+echo ===[Environment Settings - hop-server.bat]====================================
+echo.
+echo Java identified as %_HOP_JAVA%
+echo.
+echo HOP_OPTIONS=%HOP_OPTIONS%
+echo.
+echo.
+rem ===[Collect command line arguments...]======================================
+if [%1]==[DEBUG] (
+FOR /f "tokens=1*" %%x IN ("%*") DO set _cmdline=%%y
+GOTO Run
+)
+set _cmdline=%*
+
+:Run
+echo Command to start Hop will be:
+echo %_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.www.HopServer %_cmdline%
+echo.
+echo ===[Starting HopServer]=========================================================
+
%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.www.HopServer %_cmdline%
\ No newline at end of file
diff --git a/assemblies/static/src/main/resources/hop-server.sh b/assemblies/static/src/main/resources/hop-server.sh
index 06cedd3d4a7..d732505d4d8 100755
--- a/assemblies/static/src/main/resources/hop-server.sh
+++ b/assemblies/static/src/main/resources/hop-server.sh
@@ -81,16 +81,16 @@ Linux)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/linux/$(uname -m)/*"
fi
;;
Darwin)
if "${_HOP_JAVA}" -XshowSettings:properties -version 2>&1 | grep -q "os.arch = aarch64"; then
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/arm64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/arm64/*"
else
- CLASSPATH="lib/core/*:lib/beam/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
+ CLASSPATH="lib/core/*:lib/spark-client/*:lib/swt/osx/x86_64/*"
fi
HOP_OPTIONS="${HOP_OPTIONS} -XstartOnFirstThread"
;;
@@ -108,6 +108,17 @@ if [ -n "${HOP_SPARK_CLIENT_VERSION:-}" ] && [ -d "lib/spark-clients/${HOP_SPARK
CLASSPATH=$(echo "${CLASSPATH}" | sed 's|lib/spark-client/\*||g')
CLASSPATH="${CLASSPATH}:lib/spark-clients/${HOP_SPARK_CLIENT_VERSION}/*"
fi
+
+# Beam SDKs/runners live with the optional Beam plugin (not lib/beam).
+# Only on the system classpath when marketplace (or -Pbeam) has installed them.
+if [ -d "plugins/engines/beam/lib-beam" ]; then
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/lib-beam/*"
+fi
+if [ -d "plugins/engines/beam" ]; then
+ # Hop Beam plugin jars at the plugin root (exclude subdirs via non-recursive glob is fine for /*.jar intent;
+ # shell * includes jars and may pick non-jars; match historical lib/* style)
+ CLASSPATH="${CLASSPATH}:plugins/engines/beam/*"
+fi
"${_HOP_JAVA}" ${HOP_OPTIONS} -Djava.library.path="${LIBPATH}" -classpath "${CLASSPATH}" org.apache.hop.www.HopServer "$@"
EXITCODE=$?
diff --git a/assemblies/static/src/main/resources/hop.bat b/assemblies/static/src/main/resources/hop.bat
index 635a87f575b..0b624c33644 100755
--- a/assemblies/static/src/main/resources/hop.bat
+++ b/assemblies/static/src/main/resources/hop.bat
@@ -1,104 +1,112 @@
-@echo off
-
-REM
-REM Licensed to the Apache Software Foundation (ASF) under one or more
-REM contributor license agreements. See the NOTICE file distributed with
-REM this work for additional information regarding copyright ownership.
-REM The ASF licenses this file to You under the Apache License, Version 2.0
-REM (the "License"); you may not use this file except in compliance with
-REM the License. You may obtain a copy of the License at
-REM
-REM http://www.apache.org/licenses/LICENSE-2.0
-REM
-REM Unless required by applicable law or agreed to in writing, software
-REM distributed under the License is distributed on an "AS IS" BASIS,
-REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-REM See the License for the specific language governing permissions and
-REM limitations under the License.
-REM
-
-setlocal EnableDelayedExpansion
-
-REM switch to script directory
-cd /D %~dp0
-
-REM Option to change the character set of the Windows Shell to show foreign characters
-if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
-
-set LIBSPATH=lib\core;lib\beam
-set CLASSPATH=lib\core\*;lib\beam\*;lib\spark-client\*;lib\swt\win64\*
-
-REM Optional versioned Spark client pack
-if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
- set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
-)
-
-
-
-
-:NormalStart
-REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
-REM
-if not "%HOP_JAVA_HOME%"=="" (
- set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
-) else if not "%JAVA_HOME%"=="" (
- set _HOP_JAVA="%JAVA_HOME%\bin\java"
-) else (
- set _HOP_JAVA="java"
-)
-
-REM # Settings for all OSses
-REM
-
-if "%HOP_OPTIONS%"=="" set HOP_OPTIONS="-Xmx2048m"
-
-REM See if we need to enable some remote debugging options for our developers.
-REM
-FOR %%a in (%*) DO (
- if "%%~a" == "--dev-debug" (
- set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5010
- )
- if "%%~a" == "--dev-debug-wait" (
- set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5010
- )
-)
-
-REM Pass HOP variables if they're set.
-REM
-if not "%HOP_AUDIT_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
-) else (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
-)
-if not "%HOP_CONFIG_FOLDER%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
-)
-if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
-)
-if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
-)
-if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
-)
-if not "%HOP_AES_ENCODER_KEY%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
-)
-if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
- set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
-)
-
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Run
-set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
-set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
-
-REM ===[Collect command line arguments...]======================================
-REM
-set _cmdline=%*
-
-:Run
-
-%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.hop.Hop %_cmdline%%
-
+@echo off
+
+REM
+REM Licensed to the Apache Software Foundation (ASF) under one or more
+REM contributor license agreements. See the NOTICE file distributed with
+REM this work for additional information regarding copyright ownership.
+REM The ASF licenses this file to You under the Apache License, Version 2.0
+REM (the "License"); you may not use this file except in compliance with
+REM the License. You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+REM
+
+setlocal EnableDelayedExpansion
+
+REM switch to script directory
+cd /D %~dp0
+
+REM Option to change the character set of the Windows Shell to show foreign characters
+if not "%HOP_WINDOWS_SHELL_ENCODING%"=="" chcp %HOP_WINDOWS_SHELL_ENCODING%
+
+set LIBSPATH=lib\core
+set CLASSPATH=lib\core\*;lib\spark-client\*;lib\swt\win64\*
+
+REM Optional versioned Spark client pack
+if defined HOP_SPARK_CLIENT_VERSION if exist "lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\" (
+ set CLASSPATH=%CLASSPATH%;lib\spark-clients\%HOP_SPARK_CLIENT_VERSION%\*
+)
+
+REM Beam SDKs under optional plugin (plugins\engines\beam\lib-beam)
+if exist "plugins\engines\beam\lib-beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\lib-beam\*
+)
+if exist "plugins\engines\beam\" (
+ set CLASSPATH=%CLASSPATH%;plugins\engines\beam\*
+)
+
+
+
+
+:NormalStart
+REM set java primary is HOP_JAVA_HOME fallback to JAVA_HOME or default java
+REM
+if not "%HOP_JAVA_HOME%"=="" (
+ set _HOP_JAVA="%HOP_JAVA_HOME%\bin\java"
+) else if not "%JAVA_HOME%"=="" (
+ set _HOP_JAVA="%JAVA_HOME%\bin\java"
+) else (
+ set _HOP_JAVA="java"
+)
+
+REM # Settings for all OSses
+REM
+
+if "%HOP_OPTIONS%"=="" set HOP_OPTIONS="-Xmx2048m"
+
+REM See if we need to enable some remote debugging options for our developers.
+REM
+FOR %%a in (%*) DO (
+ if "%%~a" == "--dev-debug" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5010
+ )
+ if "%%~a" == "--dev-debug-wait" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5010
+ )
+)
+
+REM Pass HOP variables if they're set.
+REM
+if not "%HOP_AUDIT_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER="%HOP_AUDIT_FOLDER%"
+) else (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUDIT_FOLDER=.\audit
+)
+if not "%HOP_CONFIG_FOLDER%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_CONFIG_FOLDER="%HOP_CONFIG_FOLDER%"
+)
+if not "%HOP_SHARED_JDBC_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_SHARED_JDBC_FOLDERS="%HOP_SHARED_JDBC_FOLDERS%"
+)
+if not "%HOP_PLUGIN_BASE_FOLDERS%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLUGIN_BASE_FOLDERS="%HOP_PLUGIN_BASE_FOLDERS%"
+)
+if not "%HOP_PASSWORD_ENCODER_PLUGIN%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PASSWORD_ENCODER_PLUGIN=%HOP_PASSWORD_ENCODER_PLUGIN%
+)
+if not "%HOP_AES_ENCODER_KEY%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY=%HOP_AES_ENCODER_KEY%
+)
+if not "%HOP_AES_ENCODER_KEY_FILE%"=="" (
+ set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AES_ENCODER_KEY_FILE=%HOP_AES_ENCODER_KEY_FILE%
+)
+
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_OS=Windows
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_PLATFORM_RUNTIME=Run
+set HOP_OPTIONS=%HOP_OPTIONS% -DHOP_AUTO_CREATE_CONFIG=Y
+set HOP_OPTIONS=%HOP_OPTIONS% --add-opens java.xml/jdk.xml.internal=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.action=ALL-UNNAMED --add-opens java.base/sun.util.calendar=ALL-UNNAMED --add-opens java.security.jgss/sun.security.krb5=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED
+
+REM ===[Collect command line arguments...]======================================
+REM
+set _cmdline=%*
+
+:Run
+
+%_HOP_JAVA% -classpath %CLASSPATH% -Djava.library.path=%LIBSPATH% %HOP_OPTIONS% org.apache.hop.hop.Hop %_cmdline%%
+
diff --git a/core/src/main/java/org/apache/hop/core/AttributesContext.java b/core/src/main/java/org/apache/hop/core/AttributesContext.java
new file mode 100644
index 00000000000..c0d8dcc0057
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/AttributesContext.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Generic carrier for cross-plugin extension points.
+ *
+ *
Implements {@link IAttributes} so optional plugins can exchange namespaced group/key/value
+ * data without depending on each other. Typical groups: {@code marketplace}, resource checks
+ * ({@code resources}), etc.
+ *
+ *
Optional identity fields ({@link #projectName}, {@link #environmentName}, {@link #purpose},
+ * {@link #projectHome}, {@link #configurationFiles}) describe the surrounding project/lifecycle
+ * context when that is what is being activated; they are not required for every use of this class.
+ */
+@Getter
+@Setter
+public class AttributesContext implements IAttributes {
+
+ private String projectName;
+ private String environmentName;
+ private String purpose;
+ private String projectHome;
+ private List configurationFiles = new ArrayList<>();
+
+ private Map> attributesMap = new HashMap<>();
+
+ public AttributesContext() {
+ // empty
+ }
+
+ /** Deep-copy attributes from any {@link IAttributes} source. */
+ public AttributesContext(IAttributes source) {
+ copyAttributesFrom(source);
+ }
+
+ /**
+ * Replace this context's attributes map with a deep copy of {@code source}. Does not clear
+ * identity fields.
+ */
+ public void copyAttributesFrom(IAttributes source) {
+ attributesMap = deepCopy(source != null ? source.getAttributesMap() : null);
+ }
+
+ /** Copy this context's attributes into {@code target} (deep copy). */
+ public void copyAttributesTo(IAttributes target) {
+ if (target == null) {
+ return;
+ }
+ target.setAttributesMap(deepCopy(attributesMap));
+ }
+
+ private static Map> deepCopy(
+ Map> source) {
+ Map> copy = new HashMap<>();
+ if (source == null) {
+ return copy;
+ }
+ for (Map.Entry> entry : source.entrySet()) {
+ if (entry.getKey() == null) {
+ continue;
+ }
+ Map group =
+ entry.getValue() == null ? new HashMap<>() : new HashMap<>(entry.getValue());
+ copy.put(entry.getKey(), group);
+ }
+ return copy;
+ }
+
+ @Override
+ public void setAttributesMap(Map> attributesMap) {
+ this.attributesMap = attributesMap != null ? attributesMap : new HashMap<>();
+ }
+
+ @Override
+ public Map> getAttributesMap() {
+ return attributesMap;
+ }
+
+ @Override
+ public void setAttributes(String groupName, Map attributes) {
+ if (groupName == null) {
+ return;
+ }
+ attributesMap.put(groupName, attributes != null ? attributes : new HashMap<>());
+ }
+
+ @Override
+ public void setAttribute(String groupName, String key, String value) {
+ if (groupName == null || key == null) {
+ return;
+ }
+ Map attributes = attributesMap.get(groupName);
+ if (attributes == null) {
+ attributes = new HashMap<>();
+ attributesMap.put(groupName, attributes);
+ }
+ attributes.put(key, value);
+ }
+
+ @Override
+ public Map getAttributes(String groupName) {
+ return attributesMap.get(groupName);
+ }
+
+ @Override
+ public String getAttribute(String groupName, String key) {
+ Map attributes = attributesMap.get(groupName);
+ if (attributes == null) {
+ return null;
+ }
+ return attributes.get(key);
+ }
+
+ public List getConfigurationFiles() {
+ return configurationFiles;
+ }
+
+ public void setConfigurationFiles(List configurationFiles) {
+ this.configurationFiles =
+ configurationFiles != null ? new ArrayList<>(configurationFiles) : new ArrayList<>();
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/extension/HopExtensionPoint.java b/core/src/main/java/org/apache/hop/core/extension/HopExtensionPoint.java
index c68f78f705b..c145a789a83 100644
--- a/core/src/main/java/org/apache/hop/core/extension/HopExtensionPoint.java
+++ b/core/src/main/java/org/apache/hop/core/extension/HopExtensionPoint.java
@@ -145,6 +145,23 @@ public enum HopExtensionPoint {
HopGuiProjectAfterEnabled("Called after a project is enabled in Hop GUI"),
+ /**
+ * Called after a project (and optional lifecycle environment) is enabled. Payload is {@link
+ * org.apache.hop.core.AttributesContext}: identity fields plus namespaced {@link
+ * org.apache.hop.core.IAttributes} groups for optional plugins (marketplace, resource checks, …).
+ * Thrown {@link org.apache.hop.core.exception.HopException} aborts enablement.
+ */
+ HopProjectEnvironmentAfterEnabled(
+ "Called after a project/lifecycle environment is enabled (AttributesContext)"),
+
+ /**
+ * Called when the lifecycle environment dialog builds its tab folder so optional plugins can
+ * contribute tabs. Payload is a GUI extension object (see ui module) that carries a mutable
+ * {@link org.apache.hop.core.AttributesContext}.
+ */
+ HopGuiLifecycleEnvironmentDialogTabs(
+ "Contribute tabs to the lifecycle environment dialog (AttributesContext)"),
+
HopGuiGetControlSpaceSortOrderPrefix(
"Gets a prefix to steer the sort order of variables when using CTRL-SPACE. Defaults range from 900_ to 400_. Set prefixes in Map"),
diff --git a/core/src/test/java/org/apache/hop/core/AttributesContextTest.java b/core/src/test/java/org/apache/hop/core/AttributesContextTest.java
new file mode 100644
index 00000000000..1deaa1fea06
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/AttributesContextTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+class AttributesContextTest {
+
+ @Test
+ void setAndGetAttribute() {
+ AttributesContext ctx = new AttributesContext();
+ ctx.setAttribute("marketplace", "onEnable", "enforce");
+ assertEquals("enforce", ctx.getAttribute("marketplace", "onEnable"));
+ assertNull(ctx.getAttribute("marketplace", "missing"));
+ assertNull(ctx.getAttribute("other", "onEnable"));
+ }
+
+ @Test
+ void copyAttributesFromIsDeep() {
+ AttributesContext source = new AttributesContext();
+ source.setAttribute("resources", "minDiskGb", "10");
+ Map group = source.getAttributes("resources");
+ assertNotNull(group);
+
+ AttributesContext target = new AttributesContext(source);
+ assertEquals("10", target.getAttribute("resources", "minDiskGb"));
+
+ // Mutating source group must not change the copy
+ group.put("minDiskGb", "99");
+ assertEquals("10", target.getAttribute("resources", "minDiskGb"));
+ assertNotSame(source.getAttributesMap(), target.getAttributesMap());
+ }
+
+ @Test
+ void copyAttributesTo() {
+ AttributesContext source = new AttributesContext();
+ source.setAttribute("marketplace", "envFile", "hop-env.yaml");
+
+ AttributesContext target = new AttributesContext();
+ source.copyAttributesTo(target);
+ assertEquals("hop-env.yaml", target.getAttribute("marketplace", "envFile"));
+ }
+
+ @Test
+ void setAttributesReplacesGroup() {
+ AttributesContext ctx = new AttributesContext();
+ ctx.setAttribute("g", "a", "1");
+ Map replacement = new HashMap<>();
+ replacement.put("b", "2");
+ ctx.setAttributes("g", replacement);
+ assertNull(ctx.getAttribute("g", "a"));
+ assertEquals("2", ctx.getAttribute("g", "b"));
+ }
+}
diff --git a/docker/integration-tests/unit-tests.Dockerfile b/docker/integration-tests/unit-tests.Dockerfile
index c10cccd66b3..90e58676f12 100644
--- a/docker/integration-tests/unit-tests.Dockerfile
+++ b/docker/integration-tests/unit-tests.Dockerfile
@@ -74,6 +74,10 @@ RUN apt-get update \
COPY --chown=${JENKINS_USER}:${JENKINS_GROUP} ./assemblies/client/target/hop ${DEPLOYMENT_PATH}/hop
+# Wave 1 optional plugins (marketplace) — expect host to have run tools/install-wave1-plugins.sh
+# into assemblies/client/target/hop before this build (run-tests-docker.sh and Jenkins do this).
+# If a plugin is still missing at runtime, corresponding ITs will fail clearly.
+
# Copy gcp key
COPY --chown=${JENKINS_USER}:${JENKINS_GROUP} ${GCP_KEY_FILE} /tmp/google-key-apache-hop-it.json
diff --git a/docker/marketplace-nexus/.gitignore b/docker/marketplace-nexus/.gitignore
new file mode 100644
index 00000000000..4c49bd78f1d
--- /dev/null
+++ b/docker/marketplace-nexus/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/docker/marketplace-nexus/README.md b/docker/marketplace-nexus/README.md
new file mode 100644
index 00000000000..95321f6d434
--- /dev/null
+++ b/docker/marketplace-nexus/README.md
@@ -0,0 +1,172 @@
+
+
+# Local Nexus for Hop Marketplace testing
+
+Sonatype **Nexus Repository OSS** is the supported local Maven repository for
+developing and testing the Hop plugin marketplace.
+
+It is fully scriptable (no Pro-only APIs, no Postgres, no join keys).
+
+## Security model
+
+| Action | Who |
+|--------|-----|
+| `hop marketplace install` / GUI install | **Anonymous** read |
+| `publish-marketplace-plugins.sh` | **admin** (or a deploy user) |
+
+Do not put admin credentials in day-to-day `hop-config.json`.
+
+## Start
+
+```bash
+./docker/marketplace-nexus/start.sh
+
+# Wipe data and re-bootstrap:
+./docker/marketplace-nexus/start.sh --reset
+
+# Custom admin password:
+NEXUS_ADMIN_PASSWORD='secret' ./docker/marketplace-nexus/start.sh
+```
+
+Default after bootstrap:
+
+| | |
+|--|--|
+| UI | http://localhost:8081/ |
+| Admin | `admin` / `hop-nexus-dev` (unless overridden) |
+| Hosted repo | http://127.0.0.1:8081/repository/hop-plugins/ |
+
+Prefer **`127.0.0.1`** over `localhost` in Hop config: on some hosts `localhost` resolves
+to IPv6 (`::1`) while Docker only publishes IPv4, which surfaces as
+`java.net.ConnectException` from the marketplace client.
+
+First start can take **1–3 minutes** while Nexus initializes.
+
+## Publish marketplace plugin zips
+
+Plugin list and module paths come **only** from
+`plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/optional-plugins.yaml`.
+Add a plugin there once; this script packages/deploys it automatically.
+
+```bash
+export NEXUS_PASSWORD=hop-nexus-dev # or your NEXUS_ADMIN_PASSWORD
+
+# Package every registry module, then deploy zips to local Nexus:
+./docker/marketplace-nexus/publish-marketplace-plugins.sh --package
+
+# Or package yourself, then deploy only:
+# ./mvnw -pl … -am package -DskipTests
+./docker/marketplace-nexus/publish-marketplace-plugins.sh
+
+# Artifactory (or any Maven 2 repo):
+# export ARTIFACTORY_URL='https://artifactory.example/artifactory/hop-plugins-local'
+# export ARTIFACTORY_USER=deploy
+# export ARTIFACTORY_PASSWORD=…
+# export NEXUS_REPO_ID=artifactory
+# ./docker/marketplace-nexus/publish-marketplace-plugins.sh --package
+```
+
+`publish-wave1-plugins.sh` remains as a deprecated alias of this script.
+
+## Hop marketplace (anonymous install)
+
+Config is always **`${HOP_CONFIG_FOLDER}/hop-config.json`**. Defaults are ASF
+primary + Maven Central; add local Nexus and make it primary for dev:
+
+```bash
+cd /path/to/hop # the unzipped hop-client directory
+./hop marketplace repo add --id local-nexus \
+ --url http://127.0.0.1:8081/repository/hop-plugins/ --primary
+./hop marketplace repo list
+./hop marketplace install hop-tech-parquet
+# restore production defaults later:
+./hop marketplace repo set-primary asf
+# or: ./hop marketplace repo reset-defaults
+```
+
+Quick check:
+
+```bash
+# Expect 200 or 404, not 401
+curl -sI "http://127.0.0.1:8081/repository/hop-plugins/" | head -1
+```
+
+## Smoke test (CLI install / list / validate / apply / uninstall)
+
+```bash
+# Nexus up + marketplace plugins published + hop client unzipped
+./docker/marketplace-nexus/smoke-test.sh
+```
+
+Uses a temporary `HOP_CONFIG_FOLDER` (does not touch your real hop-config) and
+installs two small plugins into `assemblies/client/target/hop` (override with
+`HOP_DIR`).
+
+## Dummy staging (full registry verify + smoke)
+
+End-to-end dry-run of “stage plugin zips → confirm Maven layout → install”:
+
+```bash
+./docker/marketplace-nexus/start.sh
+export NEXUS_PASSWORD=hop-nexus-dev
+
+# Publish all optional-plugins.yaml zips, HTTP-verify each GAV, smoke install:
+./docker/marketplace-nexus/dummy-staging.sh
+
+# Package modules first if zips are missing:
+./docker/marketplace-nexus/dummy-staging.sh --package
+
+# Already published; only verify + smoke:
+./docker/marketplace-nexus/dummy-staging.sh --skip-publish
+```
+
+After a real ASF `release:perform`, point the same checks at the staging repo:
+
+```bash
+export REPO_URL='https://repository.apache.org/content/repositories/orgapachehop-XXXX/'
+# or NEXUS_REPO_URL=…
+./docker/marketplace-nexus/dummy-staging.sh --skip-publish
+# then drop the staging repository if it was only a dry-run
+```
+
+## Scripts
+
+| Script | Role |
+|--------|------|
+| `start.sh` | Compose up + wait + configure |
+| `configure-nexus.sh` | Anonymous + hosted repo (idempotent) |
+| `publish-marketplace-plugins.sh` | `deploy-file` of all zips from `optional-plugins.yaml` |
+| `publish-wave1-plugins.sh` | Deprecated alias of `publish-marketplace-plugins.sh` |
+| `dummy-staging.sh` | Publish (optional) + verify all registry zips + install smoke |
+| `smoke-test.sh` | Anonymous install / list / validate / apply / uninstall |
+
+## Stop
+
+```bash
+docker compose -f docker/marketplace-nexus/docker-compose.yml down
+# Wipe data:
+docker compose -f docker/marketplace-nexus/docker-compose.yml down -v
+```
+
+## Production note
+
+Released Hop plugins still go through the **ASF release process** to
+`repository.apache.org` → Maven Central. This Nexus stack only mimics Maven
+layout for local development.
diff --git a/docker/marketplace-nexus/configure-nexus.sh b/docker/marketplace-nexus/configure-nexus.sh
new file mode 100755
index 00000000000..203bf250e67
--- /dev/null
+++ b/docker/marketplace-nexus/configure-nexus.sh
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+# 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
+#
+# Idempotent Nexus OSS bootstrap for Hop marketplace:
+# - change admin password from first-boot random password if needed
+# - enable anonymous access
+# - create hosted Maven repo hop-plugins (MIXED / ALLOW for SNAPSHOT zips)
+#
+# Env:
+# NEXUS_URL default http://localhost:8081
+# NEXUS_ADMIN_USER default admin
+# NEXUS_ADMIN_PASSWORD desired admin password (default hop-nexus-dev)
+#
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BASE_URL="${NEXUS_URL:-http://localhost:8081}"
+BASE_URL="${BASE_URL%/}"
+ADMIN_USER="${NEXUS_ADMIN_USER:-admin}"
+ADMIN_PASSWORD="${NEXUS_ADMIN_PASSWORD:-hop-nexus-dev}"
+REPO_NAME="${NEXUS_REPO_NAME:-hop-plugins}"
+CONTAINER="${NEXUS_CONTAINER:-hop-marketplace-nexus}"
+
+api() {
+ # api METHOD PATH [curl args...]
+ local method="$1"
+ local path="$2"
+ shift 2
+ curl -sS -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ -X "${method}" \
+ -H 'Content-Type: application/json' \
+ -H 'Accept: application/json' \
+ "${BASE_URL}${path}" \
+ "$@"
+}
+
+http_code() {
+ local method="$1"
+ local path="$2"
+ shift 2
+ curl -sS -o /tmp/nexus-http.body -w '%{http_code}' \
+ -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ -X "${method}" \
+ -H 'Content-Type: application/json' \
+ -H 'Accept: application/json' \
+ "${BASE_URL}${path}" \
+ "$@"
+}
+
+echo "==> Resolving admin password"
+CURRENT_PASSWORD="${ADMIN_PASSWORD}"
+
+# If desired password already works, skip change
+code=$(curl -sS -o /dev/null -w '%{http_code}' -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ "${BASE_URL}/service/rest/v1/status" || echo 000)
+if [[ "${code}" != "200" ]]; then
+ # First-boot password is in the data volume
+ if docker exec "${CONTAINER}" test -f /nexus-data/admin.password 2>/dev/null; then
+ BOOT_PASSWORD=$(docker exec "${CONTAINER}" cat /nexus-data/admin.password | tr -d '\r\n')
+ echo " Using first-boot password from container volume"
+ CURRENT_PASSWORD="${BOOT_PASSWORD}"
+ code=$(curl -sS -o /dev/null -w '%{http_code}' -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ "${BASE_URL}/service/rest/v1/status" || echo 000)
+ fi
+fi
+
+if [[ "${code}" != "200" ]]; then
+ echo "ERROR: cannot authenticate as ${ADMIN_USER} (HTTP ${code})." >&2
+ echo " If you set a custom password earlier, export NEXUS_ADMIN_PASSWORD=…" >&2
+ echo " Or reset: ./start.sh --reset" >&2
+ exit 1
+fi
+echo " Authenticated as ${ADMIN_USER}"
+
+# Change password if still on first-boot password
+if [[ "${CURRENT_PASSWORD}" != "${ADMIN_PASSWORD}" ]]; then
+ echo "==> Setting admin password"
+ chg=$(curl -sS -o /tmp/nexus-chg.body -w '%{http_code}' \
+ -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ -X PUT \
+ -H 'Content-Type: text/plain' \
+ --data-binary "${ADMIN_PASSWORD}" \
+ "${BASE_URL}/service/rest/v1/security/users/${ADMIN_USER}/change-password" || echo 000)
+ # Some versions expect JSON body:
+ if [[ "${chg}" != "204" && "${chg}" != "200" ]]; then
+ chg=$(curl -sS -o /tmp/nexus-chg.body -w '%{http_code}' \
+ -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ -X PUT \
+ -H 'Content-Type: application/json' \
+ -d "{\"password\":\"${ADMIN_PASSWORD}\"}" \
+ "${BASE_URL}/service/rest/v1/security/users/${ADMIN_USER}/change-password" || echo 000)
+ fi
+ if [[ "${chg}" == "204" || "${chg}" == "200" ]]; then
+ echo " Password updated"
+ CURRENT_PASSWORD="${ADMIN_PASSWORD}"
+ else
+ echo " WARNING: change-password returned HTTP ${chg} (body: $(cat /tmp/nexus-chg.body 2>/dev/null || true))"
+ echo " Continuing with current credentials"
+ fi
+fi
+
+echo "==> Enabling anonymous access"
+anon=$(http_code PUT /service/rest/v1/security/anonymous \
+ -d '{"enabled":true,"userId":"anonymous","realmName":"NexusAuthorizingRealm"}')
+if [[ "${anon}" == "200" || "${anon}" == "204" ]]; then
+ echo " Anonymous enabled (HTTP ${anon})"
+else
+ # GET then PUT full object if needed
+ echo " WARNING: anonymous PUT HTTP ${anon}; check UI Security → Anonymous Access"
+ cat /tmp/nexus-http.body 2>/dev/null || true
+fi
+
+echo "==> Ensuring hosted Maven repository '${REPO_NAME}'"
+exists=$(curl -sS -o /dev/null -w '%{http_code}' -u "${ADMIN_USER}:${CURRENT_PASSWORD}" \
+ "${BASE_URL}/service/rest/v1/repositories/${REPO_NAME}" || echo 000)
+if [[ "${exists}" == "200" ]]; then
+ echo " Repository already exists"
+else
+ create=$(http_code POST /service/rest/v1/repositories/maven/hosted -d "{
+ \"name\": \"${REPO_NAME}\",
+ \"online\": true,
+ \"storage\": {
+ \"blobStoreName\": \"default\",
+ \"strictContentTypeValidation\": false,
+ \"writePolicy\": \"ALLOW\"
+ },
+ \"cleanup\": { \"policyNames\": [] },
+ \"component\": { \"proprietaryComponents\": false },
+ \"maven\": {
+ \"versionPolicy\": \"MIXED\",
+ \"layoutPolicy\": \"PERMISSIVE\",
+ \"contentDisposition\": \"ATTACHMENT\"
+ }
+ }")
+ if [[ "${create}" == "201" || "${create}" == "200" ]]; then
+ echo " Created hosted Maven repo '${REPO_NAME}'"
+ else
+ echo "ERROR: create repository failed HTTP ${create}" >&2
+ cat /tmp/nexus-http.body >&2 || true
+ exit 1
+ fi
+fi
+
+echo "==> Smoke test anonymous GET"
+REPO_URL="${BASE_URL}/repository/${REPO_NAME}/"
+anon_get=$(curl -sS -o /dev/null -w '%{http_code}' "${REPO_URL}" || echo 000)
+case "${anon_get}" in
+ 200|404)
+ echo " OK — anonymous can reach ${REPO_URL} (HTTP ${anon_get})"
+ ;;
+ 401|403)
+ echo " WARNING: anonymous still denied (HTTP ${anon_get})."
+ echo " UI: Settings → Security → Anonymous Access → enable"
+ ;;
+ *)
+ echo " Unexpected HTTP ${anon_get} for ${REPO_URL}"
+ ;;
+esac
+
+# Persist for publish scripts
+ENV_FILE="${SCRIPT_DIR}/.env"
+cat > "${ENV_FILE}" </dev/null || true
+echo " Wrote ${ENV_FILE}"
+
+echo "Configure complete."
diff --git a/docker/marketplace-nexus/docker-compose.yml b/docker/marketplace-nexus/docker-compose.yml
new file mode 100644
index 00000000000..e8711a0d0fa
--- /dev/null
+++ b/docker/marketplace-nexus/docker-compose.yml
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Local Sonatype Nexus Repository OSS for Hop marketplace testing.
+# Usage: ./docker/marketplace-nexus/start.sh
+#
+services:
+ nexus:
+ image: sonatype/nexus3:3.76.1
+ container_name: hop-marketplace-nexus
+ ports:
+ - "8081:8081"
+ volumes:
+ - hop-nexus-data:/nexus-data
+ # Nexus needs a few GB free under the volume; reduce heap for laptops if needed
+ environment:
+ INSTALL4J_ADD_VM_PARAMS: "-Xms512m -Xmx1536m -XX:MaxDirectMemorySize=1g -Djava.util.prefs.userRoot=/nexus-data/javaprefs"
+ healthcheck:
+ test: ["CMD", "curl", "-sf", "http://localhost:8081/service/rest/v1/status"]
+ interval: 15s
+ timeout: 10s
+ retries: 40
+ start_period: 120s
+
+volumes:
+ hop-nexus-data:
diff --git a/docker/marketplace-nexus/dummy-staging.sh b/docker/marketplace-nexus/dummy-staging.sh
new file mode 100755
index 00000000000..11e51e317fb
--- /dev/null
+++ b/docker/marketplace-nexus/dummy-staging.sh
@@ -0,0 +1,229 @@
+#!/usr/bin/env bash
+# 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
+#
+# Dummy marketplace staging: publish optional-plugin zips (from the registry) to
+# a Maven repo, verify HTTP layout, and smoke-test hop marketplace install.
+#
+# Default target is local Sonatype Nexus (same as publish-marketplace-plugins.sh).
+# This is the dry-run analogue of an ASF Nexus staging repository check.
+#
+# Prerequisites:
+# - ./docker/marketplace-nexus/start.sh (for default local Nexus)
+# - Plugin zips built, or pass --package
+# - Optional: hop client at assemblies/client/target/hop (or HOP_DIR) for install smoke
+#
+# Usage (repo root):
+# ./docker/marketplace-nexus/dummy-staging.sh
+# ./docker/marketplace-nexus/dummy-staging.sh --package
+# ./docker/marketplace-nexus/dummy-staging.sh --skip-publish # only verify + smoke
+# ./docker/marketplace-nexus/dummy-staging.sh --skip-smoke
+#
+# ASF staging (when you have apache.releases.https credentials):
+# After release:perform, set:
+# REPO_URL=https://repository.apache.org/content/repositories/orgapachehop-XXXX/
+# ./docker/marketplace-nexus/dummy-staging.sh --skip-publish
+# Then drop the staging repo if it was only a dry-run.
+#
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+LIST_SCRIPT="${ROOT}/tools/list-marketplace-plugins.sh"
+PUBLISH_SCRIPT="${SCRIPT_DIR}/publish-marketplace-plugins.sh"
+
+if [[ -f "${SCRIPT_DIR}/.env" ]]; then
+ # shellcheck disable=SC1091
+ set -a
+ source "${SCRIPT_DIR}/.env"
+ set +a
+fi
+
+VERSION="${HOP_VERSION:-2.19.0-SNAPSHOT}"
+GROUP_ID="${GROUP_ID:-org.apache.hop}"
+# NEXUS_REPO_URL / ARTIFACTORY_URL / REPO_URL (ASF staging) all accepted
+REPO_URL="${NEXUS_REPO_URL:-${ARTIFACTORY_URL:-${REPO_URL:-http://127.0.0.1:8081/repository/hop-plugins}}}"
+REPO_URL="${REPO_URL%/}/"
+HOP_DIR="${HOP_DIR:-${ROOT}/assemblies/client/target/hop}"
+
+PACKAGE=0
+SKIP_PUBLISH=0
+SKIP_SMOKE=0
+SMOKE_A="${SMOKE_PLUGIN_A:-hop-tech-dropbox}"
+SMOKE_B="${SMOKE_PLUGIN_B:-hop-transform-fake}"
+
+for arg in "$@"; do
+ case "${arg}" in
+ --package) PACKAGE=1 ;;
+ --skip-publish) SKIP_PUBLISH=1 ;;
+ --skip-smoke) SKIP_SMOKE=1 ;;
+ -h | --help)
+ sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'
+ exit 0
+ ;;
+ *)
+ echo "Unknown argument: ${arg}" >&2
+ exit 2
+ ;;
+ esac
+done
+
+echo "==> Dummy marketplace staging"
+echo " version: ${VERSION}"
+echo " repo: ${REPO_URL}"
+echo " hop dir: ${HOP_DIR}"
+echo
+
+if [[ ! -x "${LIST_SCRIPT}" ]]; then
+ chmod +x "${LIST_SCRIPT}" 2>/dev/null || true
+fi
+
+mapfile -t ENTRIES < <(HOP_VERSION="${VERSION}" "${LIST_SCRIPT}" --zips)
+if [[ ${#ENTRIES[@]} -eq 0 ]]; then
+ echo "No plugins in optional-plugins.yaml registry" >&2
+ exit 1
+fi
+echo " registry: ${#ENTRIES[@]} plugin(s)"
+
+# --- Publish (local Nexus / Artifactory) ---
+if [[ "${SKIP_PUBLISH}" -eq 0 ]]; then
+ echo
+ echo "==> Publish marketplace plugin zips"
+ if [[ ! -f "${PUBLISH_SCRIPT}" ]]; then
+ echo "Missing ${PUBLISH_SCRIPT}" >&2
+ exit 1
+ fi
+ publish_args=()
+ if [[ "${PACKAGE}" -eq 1 ]]; then
+ publish_args+=(--package)
+ fi
+ # Prefer 127.0.0.1 for deploy URL consistency with Hop client
+ export NEXUS_REPO_URL="${REPO_URL%/}"
+ export HOP_VERSION="${VERSION}"
+ "${PUBLISH_SCRIPT}" "${publish_args[@]+"${publish_args[@]}"}"
+else
+ echo
+ echo "==> Skip publish (--skip-publish)"
+fi
+
+# --- Verify HTTP Maven layout for every registry plugin ---
+echo
+echo "==> Verify zip URLs (Maven layout)"
+ok=0
+fail=0
+declare -a FAILED=()
+for entry in "${ENTRIES[@]}"; do
+ artifactId="${entry%%|*}"
+ # SNAPSHOT path uses version folder; unique timestamp filename resolved via maven-metadata later.
+ # For releases: {base}{g}/{a}/{v}/{a}-{v}.zip
+ # For SNAPSHOT: try literal SNAPSHOT zip first, then accept 200 on metadata + any zip listing.
+ gpath="${GROUP_ID//.//}"
+ base="${REPO_URL}${gpath}/${artifactId}/${VERSION}/"
+ literal_zip="${base}${artifactId}-${VERSION}.zip"
+ code=$(curl -sS -o /dev/null -w '%{http_code}' -L "${literal_zip}" || true)
+ if [[ "${code}" == "200" ]]; then
+ ok=$((ok + 1))
+ echo " OK ${artifactId} (HTTP ${code})"
+ continue
+ fi
+ # SNAPSHOT unique versions: maven-metadata.xml lists zip…
+ meta="${base}maven-metadata.xml"
+ mcode=$(curl -sS -o /tmp/hop-staging-meta.xml -w '%{http_code}' -L "${meta}" || true)
+ if [[ "${mcode}" == "200" ]]; then
+ # Prefer value that appears with extension zip (snapshotVersion block)
+ value=$(
+ tr '\n' ' ' /\n/g' \
+ | grep -F 'zip' \
+ | sed -n 's/.*\([^<]*\)<\/value>.*/\1/p' \
+ | head -1 || true
+ )
+ if [[ -z "${value}" ]]; then
+ value=$(sed -n 's/.*\([^<]*\)<\/value>.*/\1/p' /tmp/hop-staging-meta.xml | head -1 || true)
+ fi
+ if [[ -n "${value}" ]]; then
+ snap_zip="${base}${artifactId}-${value}.zip"
+ scode=$(curl -sS -o /dev/null -w '%{http_code}' -L "${snap_zip}" || true)
+ if [[ "${scode}" == "200" ]]; then
+ ok=$((ok + 1))
+ echo " OK ${artifactId} (unique SNAPSHOT ${value}.zip)"
+ continue
+ fi
+ FAILED+=("${artifactId} snap_zip=${scode} value=${value}")
+ echo " FAIL ${artifactId} (SNAPSHOT zip HTTP ${scode} for ${value})"
+ fail=$((fail + 1))
+ continue
+ fi
+ fi
+ fail=$((fail + 1))
+ FAILED+=("${artifactId} literal=${code} meta=${mcode:-n/a}")
+ echo " FAIL ${artifactId} (zip HTTP ${code}, meta HTTP ${mcode:-n/a})"
+done
+rm -f /tmp/hop-staging-meta.xml
+
+echo
+echo " verified OK=${ok} FAIL=${fail} (of ${#ENTRIES[@]})"
+if [[ "${fail}" -gt 0 ]]; then
+ echo "Missing or unreachable zips:" >&2
+ printf ' - %s\n' "${FAILED[@]}" >&2
+ exit 1
+fi
+
+# --- Hop marketplace smoke install ---
+if [[ "${SKIP_SMOKE}" -eq 1 ]]; then
+ echo
+ echo "==> Skip install smoke (--skip-smoke)"
+ echo "OK — dummy staging verify passed against ${REPO_URL}"
+ exit 0
+fi
+
+if [[ ! -x "${HOP_DIR}/hop" ]]; then
+ echo
+ echo "WARN: Hop launcher not found at ${HOP_DIR}/hop — skip install smoke."
+ echo " Build/unzip hop-client or set HOP_DIR."
+ echo "OK — dummy staging publish+verify passed against ${REPO_URL}"
+ exit 0
+fi
+
+# Ensure marketplace CLI plugin is present in the hop install (bundled plugin).
+MKT_ZIP="${ROOT}/plugins/misc/marketplace/target/hop-misc-marketplace-${VERSION}.zip"
+if [[ ! -f "${HOP_DIR}/plugins/misc/marketplace/version.xml" ]]; then
+ if [[ -f "${MKT_ZIP}" ]]; then
+ echo "==> Installing marketplace plugin into ${HOP_DIR}"
+ unzip -o -q "${MKT_ZIP}" -d "${HOP_DIR}"
+ else
+ echo "WARN: marketplace plugin not in hop install and zip missing: ${MKT_ZIP}" >&2
+ echo " Smoke may fail with 'Unmatched arguments: marketplace'." >&2
+ fi
+fi
+
+echo
+echo "==> Hop marketplace install smoke (${SMOKE_A}, ${SMOKE_B})"
+export NEXUS_REPO_URL="${REPO_URL}"
+export HOP_DIR
+export HOP_VERSION="${VERSION}"
+export SMOKE_PLUGIN_A="${SMOKE_A}"
+export SMOKE_PLUGIN_B="${SMOKE_B}"
+# Prefer 127.0.0.1 for Hop client (IPv6 localhost issues with Docker)
+export NEXUS_REPO_URL="${REPO_URL/localhost/127.0.0.1}"
+"${SCRIPT_DIR}/smoke-test.sh"
+
+echo
+echo "================================================================"
+echo "Dummy staging complete."
+echo " Repository: ${REPO_URL}"
+echo " Registry plugins verified: ${ok}"
+echo
+echo "ASF release staging (RM) — after release:perform:"
+echo " 1. Open https://repository.apache.org/#stagingRepositories"
+echo " 2. Open the orgapachehop-* staging repo content"
+echo " 3. Confirm e.g. org/apache/hop/hop-tech-parquet//…zip"
+echo " 4. REPO_URL=/ ./docker/marketplace-nexus/dummy-staging.sh --skip-publish"
+echo " 5. Drop the staging repository if this was only a dry-run"
+echo "================================================================"
diff --git a/docker/marketplace-nexus/publish-marketplace-plugins.sh b/docker/marketplace-nexus/publish-marketplace-plugins.sh
new file mode 100755
index 00000000000..bb1e461dd64
--- /dev/null
+++ b/docker/marketplace-nexus/publish-marketplace-plugins.sh
@@ -0,0 +1,195 @@
+#!/usr/bin/env bash
+# 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
+#
+# Deploy marketplace-optional plugin zips to local Nexus, Artifactory, or any
+# Maven 2 repository. Plugin list and module paths come only from:
+#
+# plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/optional-plugins.yaml
+#
+# Env:
+# NEXUS_REPO_URL or ARTIFACTORY_URL e.g. http://127.0.0.1:8081/repository/hop-plugins
+# NEXUS_USER / NEXUS_PASSWORD (or ARTIFACTORY_USER / ARTIFACTORY_PASSWORD)
+# NEXUS_REPO_ID Maven server id in settings (default hop-plugins)
+# HOP_VERSION default 2.19.0-SNAPSHOT
+# GROUP_ID default org.apache.hop (or groupId: in registry)
+#
+# Flags:
+# --package run mvn package for all registry modules before deploy
+# --dry-run print what would be deployed without calling Maven
+#
+# Usage (from repo root):
+# export NEXUS_PASSWORD=hop-nexus-dev
+# ./docker/marketplace-nexus/publish-marketplace-plugins.sh
+# ./docker/marketplace-nexus/publish-marketplace-plugins.sh --package
+#
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+LIST_SCRIPT="${ROOT}/tools/list-marketplace-plugins.sh"
+REGISTRY="${ROOT}/plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/optional-plugins.yaml"
+
+PACKAGE=0
+DRY_RUN=0
+for arg in "$@"; do
+ case "${arg}" in
+ --package) PACKAGE=1 ;;
+ --dry-run) DRY_RUN=1 ;;
+ -h | --help)
+ sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'
+ exit 0
+ ;;
+ *)
+ echo "Unknown argument: ${arg}" >&2
+ echo "Usage: $0 [--package] [--dry-run]" >&2
+ exit 2
+ ;;
+ esac
+done
+
+# Capture caller overrides before optional .env (local docker defaults must not win).
+_OV_REPO_URL="${NEXUS_REPO_URL:-${ARTIFACTORY_URL:-}}"
+_OV_REPO_ID="${NEXUS_REPO_ID:-}"
+_OV_USER="${NEXUS_USER:-${ARTIFACTORY_USER:-}}"
+_OV_PASS="${NEXUS_PASSWORD:-${ARTIFACTORY_PASSWORD:-}}"
+_OV_VERSION="${HOP_VERSION:-}"
+
+# Load .env from start/configure if present (defaults for local Nexus only)
+if [[ -f "${SCRIPT_DIR}/.env" ]]; then
+ # shellcheck disable=SC1091
+ set -a
+ # shellcheck source=/dev/null
+ source "${SCRIPT_DIR}/.env"
+ set +a
+fi
+
+# Restore explicit environment / caller exports
+[[ -n "${_OV_REPO_URL}" ]] && NEXUS_REPO_URL="${_OV_REPO_URL}" && ARTIFACTORY_URL="${_OV_REPO_URL}"
+[[ -n "${_OV_REPO_ID}" ]] && NEXUS_REPO_ID="${_OV_REPO_ID}"
+[[ -n "${_OV_USER}" ]] && NEXUS_USER="${_OV_USER}" && ARTIFACTORY_USER="${_OV_USER}"
+[[ -n "${_OV_PASS}" ]] && NEXUS_PASSWORD="${_OV_PASS}" && ARTIFACTORY_PASSWORD="${_OV_PASS}"
+[[ -n "${_OV_VERSION}" ]] && HOP_VERSION="${_OV_VERSION}"
+
+REPO_URL="${NEXUS_REPO_URL:-${ARTIFACTORY_URL:-http://127.0.0.1:8081/repository/hop-plugins}}"
+REPO_URL="${REPO_URL%/}"
+REPO_ID="${NEXUS_REPO_ID:-hop-plugins}"
+USER="${NEXUS_USER:-${NEXUS_ADMIN_USER:-${ARTIFACTORY_USER:-admin}}}"
+PASS="${NEXUS_PASSWORD:-${NEXUS_ADMIN_PASSWORD:-${ARTIFACTORY_PASSWORD:-}}}"
+VERSION="${HOP_VERSION:-2.19.0-SNAPSHOT}"
+
+echo "Deploy target: ${REPO_URL} (server id=${REPO_ID}, user=${USER})"
+
+# Prefer groupId from registry when set
+GROUP_ID="${GROUP_ID:-}"
+if [[ -z "${GROUP_ID}" && -f "${REGISTRY}" ]]; then
+ GROUP_ID="$(sed -n 's/^groupId:[[:space:]]*//p' "${REGISTRY}" | head -1 | tr -d '[:space:]"'"'"'')"
+fi
+GROUP_ID="${GROUP_ID:-org.apache.hop}"
+
+if [[ ! -x "${LIST_SCRIPT}" ]]; then
+ chmod +x "${LIST_SCRIPT}" 2>/dev/null || true
+fi
+if [[ ! -f "${LIST_SCRIPT}" ]]; then
+ echo "Missing ${LIST_SCRIPT}" >&2
+ exit 1
+fi
+
+mapfile -t ENTRIES < <(HOP_VERSION="${VERSION}" "${LIST_SCRIPT}" --zips)
+if [[ ${#ENTRIES[@]} -eq 0 ]]; then
+ echo "No plugins found in ${REGISTRY}" >&2
+ exit 1
+fi
+
+MVN="${ROOT}/mvnw"
+if [[ ! -x "${MVN}" ]]; then
+ MVN=mvn
+fi
+
+if [[ "${PACKAGE}" -eq 1 ]]; then
+ mapfile -t MODULES < <("${LIST_SCRIPT}" --modules | awk 'NF && !seen[$0]++')
+ if [[ ${#MODULES[@]} -eq 0 ]]; then
+ echo "No modulePath entries to package" >&2
+ exit 1
+ fi
+ PL_LIST="$(IFS=,; echo "${MODULES[*]}")"
+ echo "Packaging ${#MODULES[@]} module(s) (HOP_VERSION=${VERSION})…"
+ if [[ "${DRY_RUN}" -eq 1 ]]; then
+ echo "DRY-RUN: ${MVN} -pl ${PL_LIST} -am package -DskipTests"
+ else
+ "${MVN}" -pl "${PL_LIST}" -am package -DskipTests
+ fi
+fi
+
+if [[ "${DRY_RUN}" -eq 0 && -z "${PASS}" ]]; then
+ echo "Set NEXUS_PASSWORD (or NEXUS_ADMIN_PASSWORD / ARTIFACTORY_PASSWORD)." >&2
+ echo "After ./docker/marketplace-nexus/start.sh the default is hop-nexus-dev." >&2
+ exit 1
+fi
+
+SETTINGS=""
+if [[ "${DRY_RUN}" -eq 0 ]]; then
+ SETTINGS="$(mktemp)"
+ trap 'rm -f "${SETTINGS}"' EXIT
+ cat >"${SETTINGS}" <
+
+
+ ${REPO_ID}
+ ${USER}
+ ${PASS}
+
+
+
+EOF
+fi
+
+deploy_one() {
+ local artifactId="$1"
+ local rel="$2"
+ local file="${ROOT}/${rel}"
+ if [[ ! -f "${file}" ]]; then
+ echo "SKIP (not built): ${artifactId} — missing ${rel}"
+ return 0
+ fi
+ echo "Deploying ${GROUP_ID}:${artifactId}:${VERSION} → ${REPO_URL}"
+ if [[ "${DRY_RUN}" -eq 1 ]]; then
+ echo "DRY-RUN: deploy-file ${file}"
+ return 0
+ fi
+ "${MVN}" -q -s "${SETTINGS}" \
+ org.apache.maven.plugins:maven-deploy-plugin:3.1.3:deploy-file \
+ -DgroupId="${GROUP_ID}" \
+ -DartifactId="${artifactId}" \
+ -Dversion="${VERSION}" \
+ -Dpackaging=zip \
+ -Dfile="${file}" \
+ -DrepositoryId="${REPO_ID}" \
+ -Durl="${REPO_URL}" \
+ -DgeneratePom=true \
+ -DretryFailedDeploymentCount=2
+}
+
+deployed=0
+skipped=0
+for entry in "${ENTRIES[@]}"; do
+ artifactId="${entry%%|*}"
+ rel="${entry#*|}"
+ if [[ ! -f "${ROOT}/${rel}" ]]; then
+ echo "SKIP (not built): ${artifactId} — missing ${rel}"
+ skipped=$((skipped + 1))
+ continue
+ fi
+ deploy_one "${artifactId}" "${rel}"
+ deployed=$((deployed + 1))
+done
+
+echo "Done. deployed=${deployed} skipped=${skipped} base URL: ${REPO_URL}/"
+echo "Example: hop marketplace install hop-tech-parquet"
+echo "Registry: ${REGISTRY}"
diff --git a/docker/marketplace-nexus/publish-wave1-plugins.sh b/docker/marketplace-nexus/publish-wave1-plugins.sh
new file mode 100755
index 00000000000..6e7ccd3bb7a
--- /dev/null
+++ b/docker/marketplace-nexus/publish-wave1-plugins.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# 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
+#
+# Deprecated name: use publish-marketplace-plugins.sh
+# Forwards all arguments and environment to the registry-driven publisher.
+#
+set -euo pipefail
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+echo "NOTE: publish-wave1-plugins.sh is deprecated; use publish-marketplace-plugins.sh" >&2
+exec "${SCRIPT_DIR}/publish-marketplace-plugins.sh" "$@"
diff --git a/docker/marketplace-nexus/smoke-test.sh b/docker/marketplace-nexus/smoke-test.sh
new file mode 100755
index 00000000000..6b3eccf8d23
--- /dev/null
+++ b/docker/marketplace-nexus/smoke-test.sh
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+# 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
+#
+# End-to-end smoke test: local Nexus + hop marketplace CLI (anonymous).
+#
+# Prerequisites:
+# - ./docker/marketplace-nexus/start.sh
+# - ./docker/marketplace-nexus/publish-marketplace-plugins.sh
+# - Unzipped hop client at assemblies/client/target/hop (or HOP_DIR)
+#
+# Usage (from repo root):
+# ./docker/marketplace-nexus/smoke-test.sh
+#
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+HOP_DIR="${HOP_DIR:-${ROOT}/assemblies/client/target/hop}"
+REPO_URL="${NEXUS_REPO_URL:-http://127.0.0.1:8081/repository/hop-plugins/}"
+REPO_URL="${REPO_URL%/}/"
+VERSION="${HOP_VERSION:-2.19.0-SNAPSHOT}"
+
+# Small plugins for a fast round-trip (publish-marketplace-plugins must have deployed them)
+INSTALL_A="${SMOKE_PLUGIN_A:-hop-tech-dropbox}"
+INSTALL_B="${SMOKE_PLUGIN_B:-hop-transform-edi2xml}"
+
+if [[ ! -x "${HOP_DIR}/hop" ]]; then
+ echo "Hop launcher not found: ${HOP_DIR}/hop" >&2
+ echo "Build/unzip hop-client first (assemblies/client/target/hop)." >&2
+ exit 1
+fi
+
+echo "==> Checking Nexus anonymous read: ${REPO_URL}"
+code=$(curl -sS -o /dev/null -w '%{http_code}' "${REPO_URL}" || true)
+if [[ "${code}" != "200" && "${code}" != "404" ]]; then
+ echo "Nexus not reachable or not anonymous (HTTP ${code}). Run start.sh first." >&2
+ exit 1
+fi
+
+# Isolated config so we do not touch the developer's HOP_CONFIG_FOLDER
+CFG_DIR="$(mktemp -d "${TMPDIR:-/tmp}/hop-marketplace-smoke.XXXXXX")"
+AUDIT_DIR="$(mktemp -d "${TMPDIR:-/tmp}/hop-marketplace-audit.XXXXXX")"
+trap 'rm -rf "${CFG_DIR}" "${AUDIT_DIR}"' EXIT
+
+cat > "${CFG_DIR}/hop-config.json" < "${ENV_FILE}" < marketplace list (baseline)"
+run_hop marketplace list || true
+
+echo "==> marketplace install ${INSTALL_A}"
+run_hop marketplace install "${INSTALL_A}"
+
+echo "==> marketplace install ${INSTALL_B}"
+run_hop marketplace install "${INSTALL_B}"
+
+echo "==> marketplace list (expect both)"
+list_out=$(run_hop marketplace list)
+echo "${list_out}"
+echo "${list_out}" | grep -q "${INSTALL_A}"
+echo "${list_out}" | grep -q "${INSTALL_B}"
+
+echo "==> marketplace validate -f hop-env-smoke.yaml"
+run_hop marketplace validate -f "${ENV_FILE}"
+
+echo "==> marketplace uninstall ${INSTALL_B}"
+run_hop marketplace uninstall "${INSTALL_B}"
+
+echo "==> marketplace apply -f hop-env-smoke.yaml (idempotent ${INSTALL_A})"
+run_hop marketplace apply -f "${ENV_FILE}"
+
+echo "==> marketplace list (expect ${INSTALL_A} only from smoke installs; others may remain)"
+run_hop marketplace list | grep -q "${INSTALL_A}"
+
+if [[ ! -d "${HOP_DIR}/plugins" ]]; then
+ echo "FAIL: plugins/ missing under ${HOP_DIR}" >&2
+ exit 1
+fi
+
+echo
+echo "OK — marketplace smoke test passed against ${REPO_URL}"
+echo " hop install: ${HOP_DIR}"
+echo " config: ${HOP_CONFIG_FOLDER}/hop-config.json"
diff --git a/docker/marketplace-nexus/start.sh b/docker/marketplace-nexus/start.sh
new file mode 100755
index 00000000000..bd6ad60cef2
--- /dev/null
+++ b/docker/marketplace-nexus/start.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# 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
+#
+# Start Nexus, set admin password, create hop-plugins repo, enable anonymous read.
+#
+# Usage:
+# ./docker/marketplace-nexus/start.sh
+# ./docker/marketplace-nexus/start.sh --reset # wipe volumes
+# NEXUS_ADMIN_PASSWORD=secret ./docker/marketplace-nexus/start.sh
+#
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "${SCRIPT_DIR}"
+
+BASE_URL="${NEXUS_URL:-http://localhost:8081}"
+ADMIN_USER="${NEXUS_ADMIN_USER:-admin}"
+# Password we want after bootstrap (override with env)
+ADMIN_PASSWORD="${NEXUS_ADMIN_PASSWORD:-hop-nexus-dev}"
+RESET=false
+
+for arg in "$@"; do
+ case "${arg}" in
+ --reset|-r) RESET=true ;;
+ -h|--help)
+ echo "Usage: $0 [--reset]"
+ echo " NEXUS_ADMIN_PASSWORD admin password to set (default: hop-nexus-dev)"
+ exit 0
+ ;;
+ esac
+done
+
+if [[ "${RESET}" == "true" ]]; then
+ echo "Resetting Nexus volumes..."
+ docker compose down -v
+fi
+
+echo "Starting Nexus Repository OSS..."
+docker compose up -d
+
+echo "Waiting for Nexus status API (first boot can take 1–3 minutes)..."
+ready=false
+for i in $(seq 1 80); do
+ if curl -sf "${BASE_URL}/service/rest/v1/status" >/dev/null 2>&1; then
+ ready=true
+ break
+ fi
+ # Also accept 200/503 from status/writable during warmup
+ code=$(curl -s -o /dev/null -w '%{http_code}' "${BASE_URL}/service/rest/v1/status" 2>/dev/null || echo 000)
+ if [[ "${code}" == "200" ]]; then
+ ready=true
+ break
+ fi
+ printf '.'
+ sleep 3
+done
+echo
+if [[ "${ready}" != "true" ]]; then
+ echo "ERROR: Nexus did not become ready. Logs:" >&2
+ docker logs hop-marketplace-nexus 2>&1 | tail -40 >&2
+ exit 1
+fi
+echo "Nexus is up: ${BASE_URL}"
+
+export NEXUS_URL="${BASE_URL}"
+export NEXUS_ADMIN_USER="${ADMIN_USER}"
+export NEXUS_ADMIN_PASSWORD="${ADMIN_PASSWORD}"
+./configure-nexus.sh
+
+echo
+echo "Done."
+echo " UI: ${BASE_URL}/"
+echo " Admin user: ${ADMIN_USER}"
+echo " Admin pass: ${ADMIN_PASSWORD}"
+echo " Hosted repo: ${BASE_URL}/repository/hop-plugins/"
+echo
+echo "Publish (authenticated):"
+echo " export NEXUS_URL=${BASE_URL}/repository/hop-plugins"
+echo " export NEXUS_USER=${ADMIN_USER}"
+echo " export NEXUS_PASSWORD=${ADMIN_PASSWORD}"
+echo " ./publish-marketplace-plugins.sh [--package]"
+echo
+echo "Hop marketplace (anonymous read):"
+echo " \"url\": \"${BASE_URL}/repository/hop-plugins/\""
diff --git a/docker/unified.Dockerfile b/docker/unified.Dockerfile
index 4fdc6d5d018..bb63c4a4107 100644
--- a/docker/unified.Dockerfile
+++ b/docker/unified.Dockerfile
@@ -193,7 +193,7 @@ RUN mkdir -p /build/hop-web-prepared/webapps/ROOT && \
cp -r /build/assemblies/client/target/hop/config /build/hop-web-prepared/webapps/ROOT/ && \
cp -r /build/assemblies/client/target/hop/plugins /build/hop-web-prepared/ && \
cp -r /build/assemblies/client/target/hop/lib/jdbc/ /build/hop-web-prepared/jdbc-drivers && \
- cp -r /build/assemblies/client/target/hop/lib/beam/* /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
+ if [ -d /build/assemblies/client/target/hop/plugins/engines/beam/lib-beam ]; then cp -r /build/assemblies/client/target/hop/plugins/engines/beam/lib-beam/* /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/; fi && \
cp -r /build/assemblies/client/target/hop/lib/core/* /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
rm /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/hop-ui-rcp* && \
cp /build/docker/resources/run-web.sh /build/hop-web-prepared/run-web.sh && \
diff --git a/docker/web.Dockerfile b/docker/web.Dockerfile
index 5b768204a90..abe4a9ae07c 100644
--- a/docker/web.Dockerfile
+++ b/docker/web.Dockerfile
@@ -77,7 +77,7 @@ RUN groupadd -r hop -g ${HOP_GID} \
COPY ./assemblies/web/target/webapp/ "${CATALINA_HOME}"/webapps/ROOT/
COPY ./assemblies/client/target/hop/config "${CATALINA_HOME}"/webapps/ROOT/config
COPY ./assemblies/client/target/hop/lib/core "${CATALINA_HOME}"/webapps/ROOT/WEB-INF/lib
-COPY ./assemblies/client/target/hop/lib/beam "${CATALINA_HOME}"/webapps/ROOT/WEB-INF/lib
+COPY ./assemblies/client/target/hop/plugins/engines/beam/lib-beam "${CATALINA_HOME}"/webapps/ROOT/WEB-INF/lib
COPY ./assemblies/client/target/hop/plugins "${CATALINA_HOME}"/plugins
COPY ./assemblies/client/target/hop/lib/jdbc/ "${CATALINA_HOME}"/jdbc-drivers
COPY --chown=hop ./docker/resources/run-web.sh /tmp/
diff --git a/docs/hop-dev-manual/modules/ROOT/examples/marketplace/plugin-env.example.yaml b/docs/hop-dev-manual/modules/ROOT/examples/marketplace/plugin-env.example.yaml
new file mode 100644
index 00000000000..58b59c2e520
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/examples/marketplace/plugin-env.example.yaml
@@ -0,0 +1,45 @@
+# 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.
+#
+# Source of truth for marketplace-optional plugins (not bundled in hop-client).
+# full-client-env.yaml is generated from this list; GUI catalog loads it at runtime.
+# When moving a plugin out of the fat assembly, add an entry here only.
+
+# Example hop-env for a third-party plugin published to a private Nexus.
+# Adjust groupId, versions, and repository URL for your environment.
+#
+# ./hop marketplace apply -f plugin-env.example.yaml
+# # restart Hop
+#
+version: "1.0"
+# Hop client line users should run (optional Apache plugins use this as default version)
+hopVersion: "2.19.0"
+enforceOnRun: false
+
+repositories:
+ - id: sandbox
+ url: "https://nexus.sandbox.example/repository/hop-plugins/"
+ - id: asf
+ url: "https://repository.apache.org/content/groups/public/"
+
+plugins:
+ # Other plugins this product needs (install first)
+ - groupId: org.apache.hop
+ artifactId: hop-tech-parquet
+ version: "2.19.0"
+ # Your plugin (groupId:artifactId:version)
+ - groupId: com.example
+ artifactId: hop-data-vault
+ version: "1.2.0"
diff --git a/docs/hop-dev-manual/modules/ROOT/examples/marketplace/release-plugin-to-nexus.sh b/docs/hop-dev-manual/modules/ROOT/examples/marketplace/release-plugin-to-nexus.sh
new file mode 100755
index 00000000000..90e355535da
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/examples/marketplace/release-plugin-to-nexus.sh
@@ -0,0 +1,192 @@
+#!/usr/bin/env bash
+# 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
+#
+# Template: release a third-party Hop plugin zip to a Maven repository (Nexus).
+# Copy into your plugin repo as scripts/release-to-nexus.sh and adjust defaults.
+#
+# Usage:
+# export NEXUS_USER=ci NEXUS_PASSWORD=secret
+# export NEXUS_URL=https://nexus.example/repository/hop-plugins/
+# ./release-plugin-to-nexus.sh 1.2.0 --hop-version 2.19.0
+# ./release-plugin-to-nexus.sh 1.2.0 --dry-run
+#
+set -euo pipefail
+
+GROUP_ID="${GROUP_ID:-com.example}"
+ARTIFACT_ID="${ARTIFACT_ID:-hop-data-vault}"
+# Path to the packaged zip after mvn package (override if your layout differs)
+ZIP_GLOB="${ZIP_GLOB:-target/${ARTIFACT_ID}-*.zip}"
+
+NEXUS_URL="${NEXUS_URL:-http://127.0.0.1:8081/repository/hop-plugins/}"
+NEXUS_REPO_ID="${NEXUS_REPO_ID:-sandbox-hop-plugins}"
+HOP_VERSION="${HOP_VERSION:-2.19.0}"
+DRY_RUN=false
+DO_TAG=false
+PLUGIN_VERSION=""
+
+usage() {
+ cat < [options]
+
+ Plugin release version (e.g. 1.2.0 or 1.2.0-SNAPSHOT)
+
+Options:
+ --hop-version VER Hop APIs to compile/test against (default: ${HOP_VERSION})
+ --nexus-url URL Maven repo URL (default: ${NEXUS_URL})
+ --repo-id ID settings.xml server id (default: ${NEXUS_REPO_ID})
+ --group-id G Maven groupId (default: ${GROUP_ID})
+ --artifact-id A Maven artifactId (default: ${ARTIFACT_ID})
+ --tag Create git tag plugin- after successful deploy
+ --dry-run Print actions only
+ -h, --help Show this help
+
+Environment:
+ NEXUS_USER / NEXUS_PASSWORD Deploy credentials (or configure settings.xml)
+ MVN Maven command (default: mvn)
+EOF
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --hop-version) HOP_VERSION="$2"; shift 2 ;;
+ --nexus-url) NEXUS_URL="$2"; shift 2 ;;
+ --repo-id) NEXUS_REPO_ID="$2"; shift 2 ;;
+ --group-id) GROUP_ID="$2"; shift 2 ;;
+ --artifact-id) ARTIFACT_ID="$2"; shift 2 ;;
+ --tag) DO_TAG=true; shift ;;
+ --dry-run) DRY_RUN=true; shift ;;
+ -h|--help) usage; exit 0 ;;
+ -*)
+ echo "Unknown option: $1" >&2
+ usage >&2
+ exit 1
+ ;;
+ *)
+ if [[ -z "${PLUGIN_VERSION}" ]]; then
+ PLUGIN_VERSION="$1"
+ shift
+ else
+ echo "Unexpected argument: $1" >&2
+ exit 1
+ fi
+ ;;
+ esac
+done
+
+if [[ -z "${PLUGIN_VERSION}" ]]; then
+ echo "ERROR: plugin version is required" >&2
+ usage >&2
+ exit 1
+fi
+
+MVN="${MVN:-mvn}"
+NEXUS_URL="${NEXUS_URL%/}/"
+
+run() {
+ if [[ "${DRY_RUN}" == "true" ]]; then
+ echo "DRY-RUN: $*"
+ else
+ "$@"
+ fi
+}
+
+echo "==> Plugin ${GROUP_ID}:${ARTIFACT_ID}:${PLUGIN_VERSION}"
+echo " Hop compile version: ${HOP_VERSION}"
+echo " Nexus: ${NEXUS_URL} (repo id ${NEXUS_REPO_ID})"
+
+if [[ "${DRY_RUN}" != "true" ]]; then
+ if [[ -z "${NEXUS_USER:-}" || -z "${NEXUS_PASSWORD:-}" ]]; then
+ echo "NOTE: NEXUS_USER/NEXUS_PASSWORD not set; relying on Maven settings.xml server '${NEXUS_REPO_ID}'"
+ fi
+fi
+
+# Optional: align Maven project version (requires versions-maven-plugin or CI-friendly versions)
+# Uncomment if your pom version should match the release argument:
+# run ${MVN} -B versions:set -DnewVersion="${PLUGIN_VERSION}" -DgenerateBackupPoms=false
+
+echo "==> verify (hop.version=${HOP_VERSION})"
+run ${MVN} -B clean verify -Dhop.version="${HOP_VERSION}"
+
+echo "==> package zip"
+run ${MVN} -B package -DskipTests -Dhop.version="${HOP_VERSION}"
+
+ZIP_FILE=""
+# shellcheck disable=SC2086
+for f in ${ZIP_GLOB}; do
+ if [[ -f "$f" && "$f" == *"${PLUGIN_VERSION}.zip" ]]; then
+ ZIP_FILE="$f"
+ break
+ fi
+done
+if [[ -z "${ZIP_FILE}" ]]; then
+ # fallback: first matching zip
+ # shellcheck disable=SC2086
+ for f in ${ZIP_GLOB}; do
+ if [[ -f "$f" ]]; then
+ ZIP_FILE="$f"
+ break
+ fi
+ done
+fi
+
+if [[ -z "${ZIP_FILE}" || ! -f "${ZIP_FILE}" ]]; then
+ echo "ERROR: zip not found (looked for ${ZIP_GLOB}). Check assembly appendAssemblyId=false." >&2
+ exit 1
+fi
+
+echo "==> deploy ${ZIP_FILE}"
+DEPLOY=(
+ ${MVN} -B org.apache.maven.plugins:maven-deploy-plugin:3.1.3:deploy-file
+ -DgroupId="${GROUP_ID}"
+ -DartifactId="${ARTIFACT_ID}"
+ -Dversion="${PLUGIN_VERSION}"
+ -Dpackaging=zip
+ -Dfile="${ZIP_FILE}"
+ -DrepositoryId="${NEXUS_REPO_ID}"
+ -Durl="${NEXUS_URL}"
+ -DgeneratePom=true
+)
+if [[ -n "${NEXUS_USER:-}" && -n "${NEXUS_PASSWORD:-}" ]]; then
+ # Temporary settings so CI need not write ~/.m2/settings.xml
+ SETTINGS="$(mktemp)"
+ trap 'rm -f "${SETTINGS}"' EXIT
+ cat >"${SETTINGS}" <
+
+
+ ${NEXUS_REPO_ID}
+ ${NEXUS_USER}
+ ${NEXUS_PASSWORD}
+
+
+
+EOF
+ DEPLOY+=(-s "${SETTINGS}")
+fi
+run "${DEPLOY[@]}"
+
+if [[ "${DO_TAG}" == "true" ]]; then
+ TAG="plugin-${PLUGIN_VERSION}"
+ echo "==> git tag ${TAG}"
+ run git tag -a "${TAG}" -m "Release ${GROUP_ID}:${ARTIFACT_ID}:${PLUGIN_VERSION} (Hop ${HOP_VERSION})"
+fi
+
+cat </hop-tech-parquet-.zip
+org/apache/hop/hop-engines-spark//hop-engines-spark-.zip
+----
+
+The full optional list is in `plugins/misc/marketplace/.../optional-plugins.yaml`.
+
+Quick smoke (point marketplace at the **staging** repository URL as primary, then):
+
+[source,bash]
+----
+./hop marketplace install hop-tech-parquet:
+# or apply a small env file / full-client-env.yaml against staging
+----
+
+From a Hop **source** checkout with a built hop-client, you can automate the same
+checks against local Nexus or an ASF staging URL:
+
+[source,bash]
+----
+# Local dummy staging (publish + verify all optional-plugins.yaml zips + install smoke)
+./docker/marketplace-nexus/start.sh
+export NEXUS_PASSWORD=hop-nexus-dev
+./docker/marketplace-nexus/dummy-staging.sh
+
+# Against an ASF staging repository (after release:perform):
+export NEXUS_REPO_URL='https://repository.apache.org/content/repositories/orgapachehop-XXXX/'
+./docker/marketplace-nexus/dummy-staging.sh --skip-publish
+----
+
+Do not use prune on a partial install. Restart Hop after install. Drop the staging
+repository if this was only a dry-run validation.
+
== Extra checks
You can also check for unwanted binary files on the checked out tag (note: this requires the .git folder to still be present)
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/apache-release/creating-a-release.adoc b/docs/hop-dev-manual/modules/ROOT/pages/apache-release/creating-a-release.adoc
index df77bc52beb..048d1bd96d0 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/apache-release/creating-a-release.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/apache-release/creating-a-release.adoc
@@ -55,6 +55,37 @@ Build the software using the apache release profile
mvn clean install -Papache-release
----
+=== Optional marketplace plugins
+
+Large / lesser-used plugins are *not* bundled in the hop-client zip (ASF package size).
+Each plugin module with `src/assembly/assembly.xml` builds a standalone zip (Maven
+assembly with `appendAssemblyId=false`).
+
+The root `maven-release-plugin` is configured with `-P=-assemblies -DskipTests`:
+
+* **`-assemblies`** — skip the `assemblies/` reactor (hop-client / hop-web products go to
+ dist.apache.org, not Maven Central).
+* **Do not** disable the per-module **`assembly`** profile — that would drop plugin zips
+ from staging and break the marketplace.
+
+So `release:perform` publishes both the jar and the zip under the same GAV, for example:
+
+----
+https://repository.apache.org/content/groups/public/org/apache/hop/hop-tech-parquet//hop-tech-parquet-.zip
+----
+
+After staging a release candidate, verify marketplace zips in the Nexus staging
+repository (see xref:apache-release/checking-a-release.adoc[Checking a release]).
+Optional plugins are listed in
+`plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/optional-plugins.yaml`.
+
+**SNAPSHOT** testing: Jenkins deploys plugin zips to the ASF snapshots repository so
+marketplace can install them during development. Local-only testing can still use
+`docker/marketplace-nexus/` and `publish-marketplace-plugins.sh`.
+
+Keep the client under the ASF ~850 MB package limit by running
+`tools/check-assembly-size.sh` before promoting distribution artifacts.
+
we are going to use the `mvn release:prepare` command to prepare the release process
full command:
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/hopweb/index.adoc b/docs/hop-dev-manual/modules/ROOT/pages/hopweb/index.adoc
index 2174dff2bb4..5af4905ea6d 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/hopweb/index.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/hopweb/index.adoc
@@ -80,7 +80,7 @@ cp -r /tmp/hop-client/hop/lib/core/* $CATALINA_HOME/webapps/ROOT/WEB-INF/lib/
rm -f $CATALINA_HOME/webapps/ROOT/WEB-INF/lib/hop-ui-rcp-*.jar
# Step 5: Copy beam libraries
-cp -r /tmp/hop-client/hop/lib/beam/* $CATALINA_HOME/webapps/ROOT/WEB-INF/lib/
+cp -r /tmp/hop-client/hop/plugins/engines/beam/lib-beam/* $CATALINA_HOME/webapps/ROOT/WEB-INF/lib/
# Step 6: Copy plugins
cp -r /tmp/hop-client/hop/plugins $CATALINA_HOME/
@@ -200,7 +200,7 @@ sed -i '' 's&lib/core/*&../../lib/*:WEB-INF/lib/*:lib/core/*&g' ${CATALINA_HOME}
The `CLASSPATH` lines in the various scripts should look similar to the one below after editing:
-`CLASSPATH="../../lib/*:WEB-INF/lib/*:lib/core/**:lib/beam/*:lib/swt/osx/arm64/*"`
+`CLASSPATH="../../lib/*:WEB-INF/lib/*:lib/core/**:plugins/engines/beam/lib-beam/*:lib/swt/osx/arm64/*"`
Once the classpaths have been updated, make sure to make the scripts executable:
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/marketplace-publish-plugin.adoc b/docs/hop-dev-manual/modules/ROOT/pages/marketplace-publish-plugin.adoc
new file mode 100644
index 00000000000..c410953590a
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/marketplace-publish-plugin.adoc
@@ -0,0 +1,484 @@
+////
+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.
+////
+:description: How to package a third-party Hop plugin as a marketplace zip, declare Hop version and plugin dependencies, and publish releases to a private Nexus repository.
+[[MarketplacePublishPlugin]]
+= Publishing a Hop plugin to your own Maven repository
+
+This guide is for plugin authors who develop *outside* the Apache Hop repository (for example `../hop-data-vault`) and want to distribute their plugin through the Hop *marketplace*: users install a Maven GAV zip into their Hop client’s `plugins/` tree.
+
+It walks through:
+
+* Packaging a marketplace-compatible zip
+* Declaring which *Hop version* you require and which *other plugins* you depend on
+* Deploying to a private Sonatype Nexus (or any Maven 2 layout repository)
+* Automating a release for a *specific plugin version* with a script template
+
+Related reading:
+
+* xref:start-your-own-plugin.adoc[Creating your own plugin] — project skeleton and debugging
+* xref:plugin-development.adoc[Plugins Development] — plugin types and the registry
+* User manual: *Hop Marketplace* (`hop-tools/hop-marketplace.adoc`) — install, repos, hop-env apply/validate
+
+== Overview
+
+The marketplace downloads a zip from a Maven repository base URL using standard coordinates:
+
+[source,text]
+----
+{repoBase}/{groupId with dots as /}/{artifactId}/{version}/{artifactId}-{version}.zip
+----
+
+Example private Nexus (fictional):
+
+[source,text]
+----
+https://nexus.sandbox.example/repository/hop-plugins/
+ com/example/hop-data-vault/1.2.0/hop-data-vault-1.2.0.zip
+----
+
+End users configure that base URL as a marketplace repository, then install:
+
+[source,bash]
+----
+./hop marketplace repo add --id sandbox \
+ --url https://nexus.sandbox.example/repository/hop-plugins/
+./hop marketplace install com.example:hop-data-vault:1.2.0
+# restart Hop
+----
+
+Or declare the same GAV in a `hop-env.yaml` and run `hop marketplace apply -f …`.
+
+TIP: Apache Hop’s own optional plugins use `groupId` `org.apache.hop` and are published via the ASF release process. Third-party plugins should use *your* groupId (for example `com.example`).
+
+== Prerequisites
+
+* Java 21 and Maven 3.6.3+
+* A Hop version to *compile against* (Maven property, for example `hop.version=2.19.0`)
+* A Hop client for smoke-testing install (same major.minor line you support)
+* A Maven repository (Sonatype Nexus OSS/Pro, Artifactory, etc.):
+ ** Hosted Maven repository (releases and/or snapshots)
+ ** Prefer *anonymous read* for day-to-day installs; use a deploy user only for publish
+* Deploy credentials (`settings.xml` or `NEXUS_USER` / `NEXUS_PASSWORD`)
+
+For a local sandbox similar to Hop’s development stack, see `docker/marketplace-nexus/` in the Hop repository. Production teams usually run their own Nexus with corporate SSO and retention policies.
+
+== Project layout
+
+A typical standalone plugin project:
+
+[source,text]
+----
+hop-data-vault/
+ pom.xml # groupId / artifactId / version = YOUR plugin version
+ src/main/java/...
+ src/main/resources/
+ version.xml # usually ${project.version}
+ src/assembly/assembly.xml # builds the installable zip
+ dependencies.xml # optional: other Hop plugins on disk (classloader)
+ scripts/release-to-nexus.sh # release automation (see below)
+ README.md # supported Hop versions, install steps
+----
+
+Start from the remote-plugin skeleton described in xref:start-your-own-plugin.adoc[] if you do not already have a project.
+
+=== Zip layout (critical)
+
+The zip must expand *into a Hop install root* so files land under `plugins/…`:
+
+[source,text]
+----
+plugins/tech/data-vault/
+ hop-data-vault-1.2.0.jar
+ version.xml
+ lib/ # third-party jars only (not hop-core/engine/ui)
+ dependencies.xml # optional
+config/projects/samples/… # optional samples
+----
+
+Rules of thumb:
+
+* Use Maven assembly with `appendAssemblyId` *false* so the file is named `{artifactId}-{version}.zip` (marketplace expects that name on the repo path).
+* Put the plugin jar and its *runtime* third-party libraries under the plugin directory (often `lib/`).
+* *Do not* ship `hop-core`, `hop-engine`, or `hop-ui` jars — Hop already provides them.
+* Exclude jars already present in Hop’s `lib/core` when possible to avoid duplicates and classloader conflicts.
+
+Minimal assembly sketch (standalone project; adjust paths and artifactId):
+
+[source,xml]
+----
+
+ plugin
+ zip
+ .
+
+
+
+ ${project.groupId}:${project.artifactId}:jar
+
+ plugins/tech/data-vault
+
+
+ false
+ runtime
+
+ org.apache.hop:*
+
+
+ plugins/tech/data-vault/lib
+
+
+
+
+ src/main/resources/version.xml
+ plugins/tech/data-vault
+ true
+
+
+
+----
+
+Apache Hop in-tree plugins use a shared component (`assemblies/shared/hop-plugin-libs.xml`) and `${hop.plugin.libdir}`; third-party projects usually inline the layout as above.
+
+== Plugin version vs Hop version
+
+These are *different* numbers. Mixing them up is the most common packaging mistake.
+
+[cols="1,2,3"]
+|===
+| Concept | Where you set it | What it means
+
+| *Plugin release version*
+| Maven `project.version` (for example `1.2.0`)
+| The version users install: `com.example:hop-data-vault:1.2.0`
+
+| *Hop compile version*
+| Maven property `hop.version` (for example `2.19.0`)
+| Hop APIs you compiled against (`hop-engine` *provided* dependency)
+
+| *Hop runtime expectation*
+| README, release notes, sample hop-env `hopVersion`
+| Which Hop clients you support at runtime
+|===
+
+=== Compile against Hop
+
+[source,xml]
+----
+
+ 2.19.0
+
+
+
+
+ org.apache.hop
+ hop-engine
+ ${hop.version}
+ provided
+
+
+ org.apache.hop
+ hop-core
+ ${hop.version}
+ provided
+
+
+
+----
+
+Resolve Hop artifacts from Maven Central (released versions) or your mirror of `org.apache.hop`.
+
+=== Runtime compatibility (document it)
+
+Today the marketplace *does not hard-enforce* a minimum Hop version from the plugin zip. Authors should:
+
+. State supported Hop versions in the plugin README (for example “requires Hop 2.19.x”).
+. Ship a sample hop-env that sets `hopVersion` to the Hop line you target (for optional Apache plugins installed alongside yours).
+. Optionally add a small machine-readable file inside the plugin folder (convention for future tooling), for example:
+
+[source,json]
+----
+{
+ "groupId": "com.example",
+ "artifactId": "hop-data-vault",
+ "version": "1.2.0",
+ "hopVersionMin": "2.19.0",
+ "hopVersionMax": "2.19.999",
+ "requiresPlugins": [
+ { "groupId": "org.apache.hop", "artifactId": "hop-tech-parquet", "version": "2.19.0" }
+ ]
+}
+----
+
+Hop does not read this file yet; it is useful for your own installers and as a future marketplace extension.
+
+*Versioning advice:*
+
+* Treat Hop *minor* (2.19, 2.20) as an API line; re-test when users move minors.
+* Bump your plugin *major* when you break APIs or require a new Hop minor.
+* Keep a simple matrix in your README:
+
+[cols="1,1,1"]
+|===
+| Plugin version | Built with Hop | Tested on Hop
+
+| 1.2.0 | 2.19.0 | 2.19.0–2.19.x
+| 1.3.0 | 2.20.0 | 2.20.x
+|===
+
+== Other plugin dependencies
+
+There are three different “dependency” stories.
+
+=== Classpath dependencies between installed plugins (`dependencies.xml`)
+
+After install, Hop’s plugin classloader can add jars from *sibling plugin folders* using a `dependencies.xml` next to your plugin jar (same pattern as some Apache plugins such as Beam).
+
+[source,xml]
+----
+
+
+
+ ../../tech/parquet
+
+----
+
+Requirements:
+
+* The other plugin must already be installed under that path.
+* Paths must match the real layout under the Hop install’s `plugins/` tree.
+* This does *not* download anything; it only wires classloaders.
+
+=== Install-time dependencies (`hop-env.yaml`)
+
+Marketplace `apply` does *not* resolve a dependency graph. List every required plugin explicitly, *dependency plugins first*, then yours:
+
+[source,yaml]
+----
+version: "1.0"
+hopVersion: "2.19.0"
+repositories:
+ - id: sandbox
+ url: "https://nexus.sandbox.example/repository/hop-plugins/"
+ - id: asf
+ url: "https://repository.apache.org/content/groups/public/"
+plugins:
+ # Apache optional plugin your code needs on disk
+ - groupId: org.apache.hop
+ artifactId: hop-tech-parquet
+ version: "2.19.0"
+ # Your plugin
+ - groupId: com.example
+ artifactId: hop-data-vault
+ version: "1.2.0"
+----
+
+Document the same list in your README so users who install with a single `marketplace install` command know what else to install.
+
+=== Third-party Java libraries
+
+* Put them on the plugin classpath via assembly (`runtime` scope → plugin `lib/`).
+* Prefer versions compatible with jars already in Hop `lib/core`.
+* Check licenses before distributing binaries (https://www.apache.org/legal/resolved.html[ASF third-party policy] for Apache-hosted plugins; private plugins still need your own legal review).
+
+== Configure Maven and Nexus
+
+=== Server credentials
+
+In `~/.m2/settings.xml` (or CI secret injection):
+
+[source,xml]
+----
+
+
+ sandbox-hop-plugins
+ ${env.NEXUS_USER}
+ ${env.NEXUS_PASSWORD}
+
+
+----
+
+The `` must match `repositoryId` / `distributionManagement` below.
+
+=== distributionManagement (optional if you only use deploy-file)
+
+[source,xml]
+----
+
+
+ sandbox-hop-plugins
+ https://nexus.sandbox.example/repository/hop-plugins/
+
+
+ sandbox-hop-plugins
+ https://nexus.sandbox.example/repository/hop-plugins/
+
+
+----
+
+=== Deploy the zip
+
+After `mvn package` produces `target/hop-data-vault-1.2.0.zip`:
+
+[source,bash]
+----
+mvn deploy:deploy-file \
+ -DgroupId=com.example \
+ -DartifactId=hop-data-vault \
+ -Dversion=1.2.0 \
+ -Dpackaging=zip \
+ -Dfile=target/hop-data-vault-1.2.0.zip \
+ -DrepositoryId=sandbox-hop-plugins \
+ -Durl=https://nexus.sandbox.example/repository/hop-plugins/ \
+ -DgeneratePom=true
+----
+
+If your Maven module is set up so the zip is attached with `appendAssemblyId=false` and packaging is wired for deploy, `mvn deploy` can replace deploy-file.
+
+Verify anonymous (or authenticated) download:
+
+[source,bash]
+----
+curl -sI \
+ "https://nexus.sandbox.example/repository/hop-plugins/com/example/hop-data-vault/1.2.0/hop-data-vault-1.2.0.zip" \
+ | head -1
+# Expect HTTP 200 (or 401 only if you require auth for install)
+----
+
+IMPORTANT: Prefer *anonymous read* for marketplace installs. Wrong Basic auth credentials can cause HTTP 401 even when anonymous would work. Deploy credentials should stay in CI, not in hop-config for every user.
+
+== Release script (specific plugin version)
+
+A copyable template lives in the Hop repository:
+
+`docs/hop-dev-manual/modules/ROOT/examples/marketplace/release-plugin-to-nexus.sh`
+
+Copy it into your plugin repo as `scripts/release-to-nexus.sh` and adjust defaults (`GROUP_ID`, `ARTIFACT_ID`, paths).
+
+=== What the script does
+
+. Requires a *plugin version* argument (for example `1.2.0`).
+. Optional `--hop-version 2.19.0` (compile/test against that Hop line).
+. Optional Nexus URL / repository id; credentials from env or Maven settings.
+. Runs `mvn clean verify` with `-Dhop.version=…`.
+. Packages the assembly zip.
+. Deploys the zip with `deploy:deploy-file`.
+. Prints end-user install commands.
+. Supports `--dry-run` and optional `git tag`.
+
+=== Example
+
+[source,bash]
+----
+export NEXUS_USER=ci-deploy
+export NEXUS_PASSWORD='…'
+export NEXUS_URL=https://nexus.sandbox.example/repository/hop-plugins/
+
+./scripts/release-to-nexus.sh 1.2.0 --hop-version 2.19.0
+
+# SNAPSHOT to the same (or snapshot) repo:
+./scripts/release-to-nexus.sh 1.3.0-SNAPSHOT --hop-version 2.19.0-SNAPSHOT
+----
+
+=== SNAPSHOT vs release
+
+* *Release* versions (`1.2.0`) should be immutable — do not redeploy over them.
+* *SNAPSHOT* versions can be redeployed; Nexus stores unique timestamps. Marketplace resolves SNAPSHOTs via `maven-metadata.xml` when needed.
+* Use separate Nexus repos or policies for snapshots vs releases if your ops team requires it.
+
+== End-user install on a sandbox Hop
+
+. Ensure Hop is the supported line (for example 2.19.x).
+. Add your repository (CLI or hop-config):
+
+[source,bash]
+----
+./hop marketplace repo add --id sandbox \
+ --url https://nexus.sandbox.example/repository/hop-plugins/
+# optional: make it primary if ASF/Central should not be tried first
+./hop marketplace repo set-primary sandbox
+----
+
+. Install:
+
+[source,bash]
+----
+./hop marketplace install com.example:hop-data-vault:1.2.0
+# or apply a hop-env that lists dependencies + your plugin
+./hop marketplace apply -f hop-env-data-vault.yaml
+----
+
+. Restart Hop so the plugin registry reloads.
+
+. Smoke-test: open a pipeline that uses your transform/action; check logs for classloader errors.
+
+=== Troubleshooting
+
+[cols="1,2"]
+|===
+| Symptom | Likely cause
+
+| HTTP 401 / 403
+| Repo requires auth, or *wrong* Basic auth is set (clear `HOP_MARKETPLACE_*` for anonymous repos)
+
+| HTTP 404
+| Wrong groupId/artifactId/version, or zip not named `{artifactId}-{version}.zip`
+
+| Install OK but plugin missing in GUI
+| Zip layout not under `plugins/…`, or restart not done
+
+| `NoClassDefFoundError` for another plugin
+| Missing `dependencies.xml` peer or peer not installed; fix hop-env order
+
+| API / method mismatch errors
+| Plugin built against a different Hop line than the running client
+|===
+
+== Author checklist
+
+* [ ] `hop.version` set for compile (`provided` hop-engine / hop-core)
+* [ ] Plugin `version` chosen for this release
+* [ ] Assembly zip expands under `plugins/…` with correct jar + `version.xml`
+* [ ] No hop-core / hop-engine / hop-ui jars inside the zip
+* [ ] Third-party libs reviewed (size, license, overlap with Hop)
+* [ ] Other plugins documented (`dependencies.xml` and/or hop-env list)
+* [ ] Nexus allows read for installs; deploy credentials only for CI
+* [ ] Smoke install into a clean Hop client matching the supported line
+* [ ] Release script run for this version; git tag if you use tags
+* [ ] README updated with install commands and Hop compatibility matrix
+
+== Limitations (current Hop marketplace)
+
+[cols="1,2,2"]
+|===
+| Need | Today | Possible later
+
+| Minimum Hop version enforcement
+| Document in README / hop-env only
+| Marketplace reads author metadata from the zip
+
+| Automatic install of dependent plugins
+| List them explicitly in hop-env / docs
+| Dependency graph resolution
+
+| Catalog search for private plugins
+| Users install by GAV or private `query` if you add to a registry
+| Configurable search API against your Nexus
+
+| Signing
+| Optional; use Nexus checksums
+| Document signing workflow if required by your org
+|===
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/start-your-own-plugin.adoc b/docs/hop-dev-manual/modules/ROOT/pages/start-your-own-plugin.adoc
index c51e0bd94fb..2d2d6c7eec5 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/start-your-own-plugin.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/start-your-own-plugin.adoc
@@ -22,6 +22,8 @@ under the License.
This guide is to help you set up and start development on a plugin that will not be included in the Hop repository.
For more information on plugin types and how to add them to the Hop repository check out xref:plugin-development.adoc[following guide].
+To package a marketplace zip and publish releases to your own Nexus (including Hop version requirements and plugin dependencies), see xref:marketplace-publish-plugin.adoc[Publishing a Hop plugin to your own Maven repository].
+
The Apache software foundation has a strict policy on source licenses including all dependencies used to provide certain functionality.
To see which dependencies are allowed in an Apache project check https://www.apache.org/legal/resolved.html[following page].
If your plugin consists of code and dependencies in category A it can safely be added to the Hop repository and community can help maintain the code.
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-environment.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-environment.png
new file mode 100644
index 00000000000..3dd4005ce2d
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-environment.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-plugins.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-plugins.png
new file mode 100644
index 00000000000..e9f6341577c
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-plugins.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repositories.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repositories.png
new file mode 100644
index 00000000000..5d4e4602199
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repositories.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor-plugins.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor-plugins.png
new file mode 100644
index 00000000000..3709eb5c201
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor-plugins.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor.png
new file mode 100644
index 00000000000..52c4e2804fc
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-dialog-repository-editor.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-environment-drift.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-environment-drift.png
new file mode 100644
index 00000000000..c531ed1a024
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-environment-drift.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-hopgui-toolbar-icon.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-hopgui-toolbar-icon.png
new file mode 100644
index 00000000000..e082a32440d
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-hopgui-toolbar-icon.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-lifecycle-enviromnent-enforce-configuration.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-lifecycle-enviromnent-enforce-configuration.png
new file mode 100644
index 00000000000..8388b94cd6f
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-lifecycle-enviromnent-enforce-configuration.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-welcome-dialog.png b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-welcome-dialog.png
new file mode 100644
index 00000000000..33b4e010f91
Binary files /dev/null and b/docs/hop-user-manual/modules/ROOT/assets/images/marketplace/marketplace-welcome-dialog.png differ
diff --git a/docs/hop-user-manual/modules/ROOT/nav.adoc b/docs/hop-user-manual/modules/ROOT/nav.adoc
index 86fb13bc8e9..2ecf6c5a318 100644
--- a/docs/hop-user-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-user-manual/modules/ROOT/nav.adoc
@@ -511,6 +511,7 @@ under the License.
** xref:hop-server/async-web-service.adoc[Async Web Service]
* xref:hop-rest/index.adoc[Hop REST Web Application]
* xref:hop-tools/index.adoc[Hop Tools]
+** xref:hop-tools/hop-marketplace.adoc[hop marketplace]
** xref:hop-tools/hop-conf/hop-conf.adoc[hop-conf]
** xref:hop-tools/hop-encrypt.adoc[hop-encrypt]
** xref:hop-tools/hop-gui.adoc[hop-gui]
diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-conf/hop-conf.adoc b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-conf/hop-conf.adoc
index af0496cc254..7dd11e2612e 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-conf/hop-conf.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-conf/hop-conf.adoc
@@ -16,7 +16,7 @@ under the License.
////
:openvar: ${
:closevar: }
-:description: Hop Conf is a command line tool to manage environments. The hop-conf.sh script can be run with the -h flag (`./hop-conf.sh -h`) to display available options.
+:description: Hop Conf is a command line tool to manage your Hop client installation
= Hop Conf - The Hop command line configuration tool
diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-marketplace.adoc b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-marketplace.adoc
new file mode 100644
index 00000000000..f80049a632b
--- /dev/null
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-marketplace.adoc
@@ -0,0 +1,447 @@
+////
+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.
+////
+:description: Install optional Hop plugins from Apache Repository / Maven Central
+:openvar: ${
+:closevar: }
+
+:TOC:
+
+= Hop Marketplace
+
+Some Hop plugins are optional: they are not bundled in the default client zip so the download sizes stay within reason. The *marketplace* installs those plugins into the running Hop client’s `plugins/` tree from a Maven repository.
+
+== Restore the full plugin set
+
+The default hop-client zip is intentionally smaller: we made large plugins optional since version 2.19.0. To install
+*all* marketplace-optional plugins that used to ship in the download (before version 2.19),
+use the generated environment file at the installation root:
+
+[source,bash]
+----
+cd /path/to/hop
+./hop marketplace apply -f full-client-env.yaml
+
+# Please restart Hop server or GUI instances that might still be open
+----
+
+*Notes*:
+
+* Requires network access to Apache Repository / Maven Central (or a mirror configured in hop-config).
+* For SNAPSHOT builds when developing you can point the marketplace at a local Nexus that has the plugin zip files published. See folder `docker/marketplace-nexus` in the source code to run your own Nexus locally.
+* The download is large (hundreds of MB); that is expected.
+* Do *NOT* pass `--prune` unless you also want other marketplace installs removed.
+* `full-client-env.yaml` is generated at build time from marketplace plugin source file `optional-plugins.yaml` (source of truth).
+
+Beam SDKs/runners live under `plugins/engines/beam/lib-beam` (installed with the Beam plugin), not under `lib/beam`. Launch scripts add that folder to the classpath only when it exists. Developers who want Beam baked into a local hop-client build can use Maven profile `-Pbeam` (for example `./mvnw -pl assemblies/client -am package -Pbeam`).
+
+== Defaults
+
+Without extra configuration, Hop uses:
+
+. *asf* (primary) — `https://repository.apache.org/content/groups/public/`
+. *central* (fallback) — `https://repo1.maven.org/maven2/`
+
+Install tries the primary repository first, then other *enabled* repositories, until the plugin zip is found.
+
+Settings are stored under the `marketplace` key in
+
+[source]
+----
+${HOP_CONFIG_FOLDER}/hop-config.json
+----
+
+[NOTE]
+`config/hop-config.json` in your client distribution is only provided to get started but will be not survive a Hop version upgrade. Configure your own configuration.
+
+== CLI overview
+
+From the Hop install directory:
+
+[source,bash]
+----
+./hop marketplace install hop-tech-parquet
+./hop marketplace install hop-engines-spark:2.19.0
+./hop marketplace install hop-engines-spark:2.19.0 --repo asf
+./hop marketplace list
+./hop marketplace query parquet
+./hop marketplace query "spark engine"
+./hop marketplace uninstall hop-tech-parquet
+
+# Repositories
+./hop marketplace repo list
+./hop marketplace repo add --id corporate --url https://nexus.example.com/repository/hop/ --primary
+./hop marketplace repo set-primary asf
+./hop marketplace repo reset-defaults
+
+# Declarative environment (CI / Docker)
+./hop marketplace apply -f hop-env.yaml
+./hop marketplace apply -f hop-env.yaml --prune
+./hop marketplace validate -f hop-env.yaml
+./hop marketplace validate -f hop-env.yaml --strict
+----
+
+After install, apply, or uninstall, *restart Hop* so the plugin registry reloads.
+
+Coordinates for `install`: `artifactId`, `artifactId:version`, or `groupId:artifactId:version`.
+Optional `--repo ` forces a single repository (skips the fallback chain).
+
+`query` searches the local optional-plugin catalog (not Maven Central). The filter is a
+case-insensitive substring matched against artifact id, name, category, description, and install
+path. Omit the filter (or pass an empty string) to list every optional plugin. Matches may show
+`[installed]` when the plugin folder is present under the Hop install.
+
+== GUI
+
+The marketplace GUI is the primary way to install optional plugins, manage Maven repositories, and edit declarative environment files without using the CLI.
+
+=== Opening the marketplace
+
+You can open the marketplace dialog from:
+
+* *Tools → Marketplace…*
+* The Hop Gui toolbar marketplace icon
+
+image::marketplace/marketplace-hopgui-toolbar-icon.png[Hop Gui toolbar marketplace icon,50%,align="left"]
+
+On first use you may also see the welcome page that introduces the marketplace:
+
+image::marketplace/marketplace-welcome-dialog.png[Marketplace welcome dialog,75%,align="left"]
+
+The main *Plugin Marketplace* dialog has three tabs:
+
+* *Plugins* — browse, search, install, and uninstall optional plugins
+* *Environment* — edit `hop-env.yaml` / JSON environments and validate or apply them
+* *Repositories* — configure Maven repositories (same settings as the CLI / hop-config)
+
+After install, apply, or uninstall, *restart Hop* so the plugin registry reloads.
+
+=== Plugins tab
+
+image::marketplace/marketplace-dialog-plugins.png[Marketplace dialog Plugins tab,75%,align="left"]
+
+Use this tab to work with the local optional-plugin catalog:
+
+* *Search* — type three or more characters to filter by name, artifact id, category, description, or install path. Clear the filter with the clear button.
+* Table columns: *Name*, *Artifact*, *Category*, *Status*, *Description*.
+* *Status* values:
+** *Installed* — present on disk with a marketplace install receipt (version shown when known)
+** *Present on disk* — plugin folder exists but no marketplace receipt
+** *Not installed* — not found under `plugins/`
+* *Install* / *Uninstall* / *Refresh* — act on the selected plugin (uninstall requires a marketplace receipt)
+
+Install uses the *primary* repository first, then other *enabled* repositories (see <>).
+
+=== Environment tab
+
+image::marketplace/marketplace-dialog-environment.png[Marketplace dialog Environment tab,75%,align="left"]
+
+This tab embeds the full environment editor (no separate “Edit…” window). It is the GUI equivalent of `hop marketplace validate` / `apply` and of editing `hop-env.yaml` by hand.
+
+==== File toolbar
+
+* *New* — empty environment specification
+* *Open…* — open a `hop-env.yaml` / `.yml` / `.json` file
+* *Save* / *Save As…* — write YAML or JSON (by extension)
+* Filename *combo* — current path plus recently used environment files (remembered via the Hop audit list for this namespace). Selecting an entry loads that file.
+* On open, Hop prefers the last file you edited in this dialog; otherwise it may pre-select `full-client-env.yaml` at the install root when that file exists.
+
+==== Nested editor tabs
+
+[cols="1,3"]
+|===
+| Tab | Contents
+
+| *General*
+| Spec `version`, `hopVersion`, and `enforceOnRun` (when true, hop-run can fail if the install drifts from this file; also controlled by `-Dhop.env.enforce=true`).
+
+| *Repositories*
+| Ordered list of Maven repositories for *this environment file* (first row is primary). Optional username/password. *Import from hop-config…* copies enabled repositories from hop-config (one-shot; env-file repos stay independent of the main *Repositories* tab).
+
+| *Plugins*
+| Plugin GAVs to install. *From catalog…* multi-selects from the optional-plugin catalog. Leave Group ID blank to use `org.apache.hop`. Version can default from *Hop version* on General when applying.
+
+| *Dependencies*
+| Optional jars (for example JDBC) with target directory under Hop home (default `lib/jdbc`).
+|===
+
+==== Validate and Apply
+
+At the bottom of the Environment tab:
+
+* *Validate* — compare the local install to the saved environment file without downloading.
+* *Apply* — install missing plugins and dependencies declared in the file.
+* *Prune extras on apply* — same as CLI `apply --prune` (confirm before running).
+* *Strict validate* — also fail when extra marketplace-installed plugins (receipts under `plugins/.marketplace/`) are not listed in the file.
+
+If validation finds drift, Hop shows a report. Use *Apply* to fix, then restart Hop.
+
+image::marketplace/marketplace-environment-drift.png[Marketplace environment drift report,75%,align="left"]
+
+[[marketplace-gui-repositories]]
+=== Repositories tab
+
+image::marketplace/marketplace-dialog-repositories.png[Marketplace dialog Repositories tab,75%,align="left"]
+
+Configure the global marketplace repository list (stored under `marketplace` in `${HOP_CONFIG_FOLDER}/hop-config.json` — the same config the CLI uses):
+
+* Table columns: *Primary*, *Enabled*, *Id*, *Name*, *URL*, *Auth*
+* *Add* / *Edit* / *Remove* — maintain repository entries
+* *Set primary* — install tries this repository first
+* *Up* / *Down* — order fallbacks after the primary
+* *Reset defaults* — Apache Repository (primary) + Maven Central
+* *Save* — write hop-config (required to keep changes)
+
+Add or edit a repository with id, name, URL, optional credentials, and primary/enabled flags:
+
+image::marketplace/marketplace-dialog-repository-editor.png[Add or edit marketplace repository,60%,align="left"]
+
+=== Projects lifecycle environments
+
+When the *Projects* plugin is installed, each lifecycle environment can store marketplace-specific *attributes* (namespaced under the `marketplace` group). On the environment properties dialog, open the *Marketplace plugins* tab:
+
+image::marketplace/marketplace-lifecycle-enviromnent-enforce-configuration.png[Lifecycle environment Marketplace plugins configuration,75%,align="left"]
+
+Typical options:
+
+* *Environment file* — path to a `hop-env.yaml` (variables allowed, for example under the project home)
+* *On enable* — `off` (no check), `warn` (log or dialog on drift), or `enforce` (fail enabling the environment if the install does not match)
+* *Strict* — treat extra marketplace plugins as drift
+* *Auto-apply missing plugins on enable* — optional; off by default (use only on controlled machines)
+
+If *On enable* is unset, purpose-based defaults apply: *Production* → enforce, *Testing* / *Acceptance* → warn, *Development* / CI-style purposes → off.
+
+This complements hop-env `enforceOnRun` (checked at hop-run) and the Environment tab *Validate* / *Apply* actions (manual checks from the marketplace dialog).
+
+== Declarative environments (`hop-env.yaml`)
+
+For CI/CD, Docker, and reproducible servers you can declare the required plugins (and optional jars) in a file, commit it to Git, and apply it without using the GUI.
+
+Supported file names / formats:
+
+* `hop-env.yaml` / `hop-env.yml` (YAML)
+* `hop-env.json` (JSON — same field names)
+
+=== `hop marketplace apply`
+
+Installs missing plugins and dependencies declared in the environment file. Already-satisfied plugins are skipped.
+
+[cols="1,3"]
+|===
+| Option | Description
+
+| `-f`, `--file` *(required)*
+| Path to `hop-env.yaml` or `hop-env.json`.
+
+| `--prune`
+| After installing what the file requires, *uninstall* marketplace-managed plugins that have an install receipt but are *not* listed under `plugins`. Core/bundled plugins and plugins present on disk without a marketplace receipt are not removed by prune.
+|===
+
+Examples:
+
+[source,bash]
+----
+# Install everything listed (idempotent)
+./hop marketplace apply -f hop-env.yaml
+
+# Also remove marketplace plugins that are no longer in the file
+./hop marketplace apply -f ./deploy/hop-env.yaml --prune
+----
+
+Behaviour notes:
+
+* Repositories listed in the file override hop-config for this apply (first entry is primary; later entries are fallbacks). If the file omits `repositories`, hop-config repositories are used.
+* Plugin `version` defaults to `hopVersion` in the file, then the marketplace/hop version.
+* Plugins already on disk without a marketplace receipt (for example installed offline with `tools/install-wave1-plugins.sh`) count as satisfied and are not re-downloaded.
+* Dependencies are downloaded as jars into `target` (default `lib/jdbc`).
+
+Always restart Hop after a successful apply when new plugins were installed.
+
+=== `hop marketplace validate`
+
+Compares the local install to the environment file *without* downloading or installing anything. Useful in CI as a pre-flight check.
+
+[cols="1,3"]
+|===
+| Option | Description
+
+| `-f`, `--file`
+| Path to the environment file. If omitted, Hop discovers a file (see <>).
+
+| `--strict`
+| Also fail when *extra* marketplace-installed plugins (receipts under `plugins/.marketplace/`) are present that are not listed in the file. Without `--strict`, extras are ignored.
+|===
+
+Exit codes:
+
+* *0* — environment matches (prints `OK: environment matches …`)
+* *non-zero* — drift detected or error (prints a drift report and suggests `hop marketplace apply -f …`)
+
+What counts as drift (always hard-fail):
+
+* *Missing plugin* — not on disk and no install receipt
+* *Version mismatch* — receipt version differs from the version required in the file
+* *Missing dependency* — jar not found under the dependency `target` directory
+
+Examples:
+
+[source,bash]
+----
+./hop marketplace validate -f hop-env.yaml
+./hop marketplace validate -f hop-env.yaml --strict
+
+# Discover hop-env under PROJECT_HOME or the Hop install
+export PROJECT_HOME=/path/to/my-project
+./hop marketplace validate
+----
+
+=== hop-run enforcement
+
+When a hop-env file is found and enforcement is on, `hop-run` validates before execution and aborts on missing plugins/deps or version mismatches (same checks as validate without `--strict`).
+
+Enable enforcement either:
+
+* in the file: `enforceOnRun: true`, or
+* with a system property: `-Dhop.env.enforce=true` (or `Y`)
+
+If a hop-env file is present but `enforceOnRun` is false and the property is unset, hop-run only logs that the file was found and skips the check.
+
+[[hop-env-discovery]]
+=== How the environment file is discovered
+
+Used by `validate` (when `-f` is omitted) and by hop-run:
+
+. Explicit path: `-f` / `--file` (validate only), or variable / property `HOP_ENV_FILE` / `hop.env.file`
+. Under `PROJECT_HOME` (system property or env): `hop-env.yaml`, then `.yml`, then `.json`
+. Under the Hop install root: same file names
+
+=== `hop-env.yaml` format
+
+Full example:
+
+[source,yaml]
+----
+# Schema version of this file format (optional; default "1.0")
+version: "1.0"
+
+# Default plugin version when a plugin entry omits "version"
+hopVersion: "2.19.0"
+
+# If true, hop-run fails when the install drifts from this file
+enforceOnRun: false
+
+# Maven repositories for apply (optional).
+# First entry is primary; later entries are fallbacks for that apply only.
+# If omitted, hop-config marketplace.repositories are used.
+repositories:
+ - id: asf
+ url: "https://repository.apache.org/content/groups/public/"
+ - id: central
+ url: "https://repo1.maven.org/maven2/"
+ # Private mirror example:
+ # - id: corporate
+ # url: "https://nexus.example.com/repository/hop-plugins/"
+ # username: reader
+ # password: "…" # prefer env HOP_MARKETPLACE_PASSWORD for secrets
+
+# Required optional plugins (installed into plugins/)
+plugins:
+ - artifactId: hop-tech-parquet
+ version: "2.19.0" # optional if hopVersion is set
+ - groupId: org.apache.hop # optional; default org.apache.hop
+ artifactId: hop-engines-spark
+ version: "2.19.0"
+ # Beam engine plugin only (Beam SDKs ship under plugins/engines/beam/lib-beam with the plugin):
+ # - artifactId: hop-engines-beam
+ # version: "2.19.0"
+
+# Optional non-plugin jars (e.g. JDBC drivers)
+dependencies:
+ - groupId: org.postgresql
+ artifactId: postgresql
+ version: "42.7.3"
+ target: lib/jdbc # optional; default lib/jdbc
+----
+
+==== Top-level fields
+
+[cols="1,1,3"]
+|===
+| Field | Required | Description
+
+| `version`
+| No
+| Format version of the hop-env file itself (default `1.0`).
+
+| `hopVersion`
+| Recommended
+| Default version for plugin entries that omit `version`. Also used when aligning plugins with a Hop release.
+
+| `enforceOnRun`
+| No
+| When `true`, hop-run validates this file before running and fails on missing plugins/deps or version mismatches.
+
+| `repositories`
+| No
+| List of Maven base URLs used when *applying* this file. Order: first = primary, rest = fallbacks. Each item: `id`, `url`, optional `username` / `password`.
+
+| `plugins`
+| No
+| List of optional Hop plugins to install. Each item: `artifactId` (required), optional `groupId` (default `org.apache.hop`), optional `version` (default `hopVersion` / Hop version).
+
+| `dependencies`
+| No
+| Extra jars (not Hop plugin zips). Each item: `groupId`, `artifactId`, `version` (all required), optional `target` directory under the Hop install (default `lib/jdbc`). Downloaded as `artifactId-version.jar`.
+|===
+
+==== Minimal examples
+
+*Only plugins, ASF defaults from hop-config:*
+
+[source,yaml]
+----
+version: "1.0"
+hopVersion: "2.19.0"
+plugins:
+ - artifactId: hop-tech-parquet
+ - artifactId: hop-tech-cassandra
+----
+
+*CI gate (no install):*
+
+[source,bash]
+----
+./hop marketplace validate -f hop-env.yaml --strict || exit 1
+----
+
+*Docker / pipeline install then run:*
+
+[source,bash]
+----
+./hop marketplace apply -f /files/hop-env.yaml
+# restart container or new process so plugins load
+./hop-run.sh -f /files/main.hwf
+----
+
+A longer sample ships with the marketplace plugin sources (`plugins/misc/marketplace/src/main/samples/hop-env.example.yaml`) and may appear under samples after the plugin is installed.
+
+== Enterprise / air-gapped
+
+Point marketplace repositories at your internal Nexus or Artifactory mirror of `org.apache.hop` plugin zips. Prefer anonymous read where possible; for private repos set Basic auth on the repository entry (in hop-config or in hop-env `repositories`) or use `HOP_MARKETPLACE_USERNAME` / `HOP_MARKETPLACE_PASSWORD`. Wrong credentials can cause HTTP 401 even when anonymous access would work.
+
+== For plugin authors
+
+If you develop a third-party plugin (outside the Hop repository) and want to publish installable zips to your own Nexus, see the developer guide *Publishing a Hop plugin to your own Maven repository* in the Hop Development Manual (`marketplace-publish-plugin.adoc`). It covers packaging, Hop version requirements, other plugin dependencies, and a sample release script.
diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/index.adoc b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/index.adoc
index 58d917c38aa..8e49c6561d3 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/index.adoc
@@ -22,6 +22,7 @@ Even though Hop developers will spend the majority of their time in Hop Gui, Hop
This page provides an overview of the tools that are available in the platform.
* xref:hop-tools/hop.adoc[Hop]
+* xref:hop-tools/hop-marketplace.adoc[Hop Marketplace]
* xref:hop-tools/hop-conf/hop-conf.adoc[Hop Conf]
* xref:hop-tools/hop-encrypt.adoc[Hop Encrypt]
* xref:hop-tools/hop-gui.adoc[Hop Gui]
diff --git a/integration-tests/scripts/run-tests-docker.sh b/integration-tests/scripts/run-tests-docker.sh
index f6ebeac88bf..8d70ef94bc0 100755
--- a/integration-tests/scripts/run-tests-docker.sh
+++ b/integration-tests/scripts/run-tests-docker.sh
@@ -275,6 +275,16 @@ else
echo "Skipping client unzip (CLIENT_UNZIP=${CLIENT_UNZIP}, using existing ${HOP_DIR})"
fi
+# Optional plugins (Wave 1) are not in hop-client.zip; install from reactor zips for ITs.
+if [ -x "${REPO_ROOT}/tools/install-wave1-plugins.sh" ]; then
+ echo "Installing Wave 1 marketplace plugins into ${HOP_DIR} for integration tests"
+ "${REPO_ROOT}/tools/install-wave1-plugins.sh" "${HOP_DIR}" || {
+ echo "WARNING: install-wave1-plugins.sh reported errors; some ITs may fail if plugins are missing"
+ }
+else
+ echo "WARNING: tools/install-wave1-plugins.sh not found; optional plugins not installed into ${HOP_DIR}"
+fi
+
# Versioned Spark client packs are not in the client zip. Re-materialise after unzip so
# HOP_SPARK_CLIENT_VERSION=… finds lib/spark-clients// (includes spark-streaming, etc.).
# Also copy the selected pack into lib/spark-client/ so the default driver classpath always
@@ -324,74 +334,108 @@ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-base.yaml build --build-
# project that actually references the fat jar is about to run.
BEAM_IMAGE_BUILT="false"
+write_surefire_skipped() {
+ local name="$1"
+ local reason="$2"
+ local report="${CURRENT_DIR}/../surefire-reports/surefile_${name}.xml"
+ mkdir -p "${CURRENT_DIR}/../surefire-reports"
+ cat >"${report}" <
+
+
+
+EOF
+}
+
+write_surefire_env_failure() {
+ local name="$1"
+ local detail="$2"
+ local report="${CURRENT_DIR}/../surefire-reports/surefile_${name}.xml"
+ mkdir -p "${CURRENT_DIR}/../surefire-reports"
+ cat >"${report}" <
+
+${detail}
+
+EOF
+}
+
# Loop over project folders
for d in "${CURRENT_DIR}"/../${PROJECT_NAME}/; do
+ if [[ "$d" == *"scripts/" ]] || [[ "$d" == *"surefire-reports/" ]] || [[ "$d" == *"hopweb/" ]]; then
+ continue
+ fi
- if [[ "$d" != *"scripts/" ]] && [[ "$d" != *"surefire-reports/" ]] && [[ "$d" != *"hopweb/" ]]; then
- # If there is a file called disabled.txt the project is disabled
- if [ ! -f "$d/disabled.txt" ]; then
-
- PROJECT_NAME=$(basename $d)
+ # Normalize project name from the folder we are iterating
+ PROJECT_NAME=$(basename "${d}")
- echo "Project name: ${PROJECT_NAME}"
- echo "project path: $d"
- echo "docker compose path: ${DOCKER_FILES_DIR}"
+ # If there is a file called disabled.txt the project is disabled — do not pretend Docker failed
+ if [ -f "$d/disabled.txt" ]; then
+ echo "Project ${PROJECT_NAME} is disabled (disabled.txt present); skipping."
+ if [ "${SUREFIRE_REPORT}" = "true" ]; then
+ write_surefire_skipped "${PROJECT_NAME}" "Project disabled via disabled.txt"
+ fi
+ continue
+ fi
- # If this project references the Hop fat jar (Beam runners), make sure hop-beam-image exists.
- # Built once per run, and only when such a project is actually enabled.
- if [ "${BEAM_IMAGE_BUILT}" != "true" ] && grep -rqs "hop-fatjar.jar" "$d" 2>/dev/null; then
- echo "Project ${PROJECT_NAME} needs the Hop fat jar; building hop-beam-image (once)."
- if [ -n "${HOP_SPARK_CLIENT_VERSION}" ]; then
- echo "Spark client pack for fat jar: ${HOP_SPARK_CLIENT_VERSION}"
- fi
- HOP_SPARK_CLIENT_VERSION="${HOP_SPARK_CLIENT_VERSION}" \
- docker compose -f ${DOCKER_FILES_DIR}/integration-tests-beam-base.yaml build \
- --build-arg HOP_SPARK_CLIENT_VERSION="${HOP_SPARK_CLIENT_VERSION}"
- EXECUTED_COMPOSE_FILES=("${EXECUTED_COMPOSE_FILES[@]}" "${DOCKER_FILES_DIR}/integration-tests-beam-base.yaml")
- BEAM_IMAGE_BUILT="true"
- fi
+ echo "Project name: ${PROJECT_NAME}"
+ echo "project path: $d"
+ echo "docker compose path: ${DOCKER_FILES_DIR}"
- # Check if specific compose exists
+ # If this project references the Hop fat jar (Beam runners), make sure hop-beam-image exists.
+ # Built once per run, and only when such a project is actually enabled.
+ if [ "${BEAM_IMAGE_BUILT}" != "true" ] && grep -rqs "hop-fatjar.jar" "$d" 2>/dev/null; then
+ echo "Project ${PROJECT_NAME} needs the Hop fat jar; building hop-beam-image (once)."
+ if [ -n "${HOP_SPARK_CLIENT_VERSION}" ]; then
+ echo "Spark client pack for fat jar: ${HOP_SPARK_CLIENT_VERSION}"
+ fi
+ HOP_SPARK_CLIENT_VERSION="${HOP_SPARK_CLIENT_VERSION}" \
+ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-beam-base.yaml build \
+ --build-arg HOP_SPARK_CLIENT_VERSION="${HOP_SPARK_CLIENT_VERSION}"
+ EXECUTED_COMPOSE_FILES=("${EXECUTED_COMPOSE_FILES[@]}" "${DOCKER_FILES_DIR}/integration-tests-beam-base.yaml")
+ BEAM_IMAGE_BUILT="true"
+ fi
- if [ -n "${TEST_FILTER}" ]; then
- echo "TEST_FILTER: ${TEST_FILTER}"
- fi
+ if [ -n "${TEST_FILTER}" ]; then
+ echo "TEST_FILTER: ${TEST_FILTER}"
+ fi
- if [ -f "${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml" ]; then
- echo "Project compose exists."
- EXECUTED_COMPOSE_FILES=("${EXECUTED_COMPOSE_FILES[@]}" "${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml")
- # Rebuild project images so SPARK_VERSION (and similar) build args take effect.
- # hop_server also must rebuild: its hop-server service image (apache/hop:Development
- # from docker/Dockerfile) otherwise stays cached and can miss client-side assembly
- # plugins needed by remote-export ITs (main-0008/0009/0010).
- if [ "${PROJECT_NAME}" = "spark" ]; then
- echo "Spark IT cluster version: ${SPARK_VERSION} (hadoop ${HADOOP_VERSION})"
- PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} SPARK_VERSION=${SPARK_VERSION} HADOOP_VERSION=${HADOOP_VERSION} SPARK_BASE_URL=${SPARK_BASE_URL} \
- docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --build --abort-on-container-exit
- elif [ "${PROJECT_NAME}" = "hop_server" ]; then
- echo "Rebuilding hop_server images so remote Hop Server matches current assemblies"
- PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
- docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --build --abort-on-container-exit
- else
- PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
- docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --abort-on-container-exit
- fi
- else
- echo "Project compose does not exists."
- PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
- docker compose -f ${DOCKER_FILES_DIR}/integration-tests-base.yaml up --abort-on-container-exit
- fi
+ COMPOSE_EXIT=0
+ if [ -f "${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml" ]; then
+ echo "Project compose exists."
+ EXECUTED_COMPOSE_FILES=("${EXECUTED_COMPOSE_FILES[@]}" "${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml")
+ # Rebuild project images so SPARK_VERSION (and similar) build args take effect.
+ # hop_server also must rebuild: its hop-server service image (apache/hop:Development
+ # from docker/Dockerfile) otherwise stays cached and can miss client-side assembly
+ # plugins needed by remote-export ITs (main-0008/0009/0010).
+ if [ "${PROJECT_NAME}" = "spark" ]; then
+ echo "Spark IT cluster version: ${SPARK_VERSION} (hadoop ${HADOOP_VERSION})"
+ PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} SPARK_VERSION=${SPARK_VERSION} HADOOP_VERSION=${HADOOP_VERSION} SPARK_BASE_URL=${SPARK_BASE_URL} \
+ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --build --abort-on-container-exit \
+ || COMPOSE_EXIT=$?
+ elif [ "${PROJECT_NAME}" = "hop_server" ]; then
+ echo "Rebuilding hop_server images so remote Hop Server matches current assemblies"
+ PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
+ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --build --abort-on-container-exit \
+ || COMPOSE_EXIT=$?
+ else
+ PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
+ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-${PROJECT_NAME}.yaml up --abort-on-container-exit \
+ || COMPOSE_EXIT=$?
fi
+ else
+ echo "Project compose does not exists."
+ PROJECT_NAME=${PROJECT_NAME} TEST_FILTER=${TEST_FILTER} SKIP_GOOGLE_SHEETS=${SKIP_GOOGLE_SHEETS} \
+ docker compose -f ${DOCKER_FILES_DIR}/integration-tests-base.yaml up --abort-on-container-exit \
+ || COMPOSE_EXIT=$?
fi
- # Create final report
+ # Create final report only when the project was actually run and no report was produced
if [ "${SUREFIRE_REPORT}" = "true" ]; then
if [ ! -f "${CURRENT_DIR}/../surefire-reports/surefile_${PROJECT_NAME}.xml" ]; then
- echo "" >"${CURRENT_DIR}"/../surefire-reports/surefile_${PROJECT_NAME}.xml
- echo "" >>"${CURRENT_DIR}"/../surefire-reports/surefile_${PROJECT_NAME}.xml
- echo "" >>"${CURRENT_DIR}"/../surefire-reports/surefile_${PROJECT_NAME}.xml
- echo "" >>"${CURRENT_DIR}"/../surefire-reports/surefile_${PROJECT_NAME}.xml
+ write_surefire_env_failure "${PROJECT_NAME}" \
+ "Could not start docker environment for ${PROJECT_NAME} (compose exit ${COMPOSE_EXIT}). Check docker compose logs above."
fi
fi
done
diff --git a/plugins/engines/beam/pom.xml b/plugins/engines/beam/pom.xml
index 13714cfa613..e13686c5270 100644
--- a/plugins/engines/beam/pom.xml
+++ b/plugins/engines/beam/pom.xml
@@ -1659,4 +1659,48 @@
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ assembly-beam-libs
+
+ single
+
+ package
+
+ true
+
+ ${project.basedir}/src/assembly/assembly-libs.xml
+
+
+
+
+
+ assembly_package
+
+ single
+
+ package
+
+ false
+
+ ${project.basedir}/src/assembly/assembly.xml
+
+
+
+
+
+
+
+
diff --git a/plugins/engines/beam/src/assembly/assembly-beam-runtime.xml b/plugins/engines/beam/src/assembly/assembly-beam-runtime.xml
new file mode 100644
index 00000000000..369c7ca8325
--- /dev/null
+++ b/plugins/engines/beam/src/assembly/assembly-beam-runtime.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+ runtime
+
+ org.apache.hop:hop-engines-beam:jar
+ org.apache.hadoop:*:jar
+ org.apache.hadoop.thirdparty:*:jar
+ org.yaml:snakeyaml:jar
+ com.google.protobuf:protobuf-java:jar
+ com.google.protobuf:protobuf-java-util:jar
+ org.jetbrains:annotations:jar
+ com.google.auto.value:auto-value-annotations:jar
+ org.apache.beam:beam-vendor-guava-32_1_2-jre:jar
+ com.github.luben:zstd-jni:jar
+ com.google.auth:google-auth-library-oauth2-http:jar
+ com.google.oauth-client:google-oauth-client:jar
+ com.google.api-client:google-api-client:jar
+ com.google.http-client:google-http-client:jar
+ com.google.http-client:google-http-client-apache-v2:jar
+ com.google.http-client:google-http-client-appengine:jar
+ google-http-client-gson:jar
+ com.google.http-client:google-http-client-jackson2:jar
+ com.google.android:annotations:jar
+ org.codehaus.mojo:animal-sniffer-annotations:jar
+ com.google.api:api-common:jar
+ com.google.auto.value:auto-value-annotations:jar
+ com.google.guava::jar
+ org.jetbrains.kotlin::jar
+ io.netty::jar
+ org.codehaus.mojo:animal-sniffer-annotations:jar
+ io.projectreactor.netty::jar
+ io.grpc:grpc-netty-shaded:jar
+ io.grpc:grpc-xds:jar
+ org.apache.kafka::jar
+ org.xerial.snappy:snappy-java:jar
+ org.eclipse.jetty::jar
+ org.eclipse.jetty.compression::jar
+ com.google.cloud:google-cloud-storage:jar
+ io.grpc::jar
+ com.google.cloud:google-cloud-core-grpc:jar
+ com.google.api.grpc:grpc-google-cloud-storage-v2:jar
+ com.google.api:gax-grpc:jar
+ com.google.api.grpc::jar
+ org.checkerframework:checker-qual:jar
+ io.confluent:common-utils:jar
+ commons-cli:commons-cli:jar
+ commons-codec:commons-codec:jar
+ commons-logging:commons-logging:jar
+ org.apache.commons:commons-text:jar
+ com.google.cloud.opentelemetry:detector-resources-support:jar
+ com.google.errorprone:error_prone_annotations:jar
+ com.google.cloud.opentelemetry:exporter-metrics:jar
+ com.google.api:gax:jar
+ com.google.api:gax-httpjson:jar
+ com.google.apis:google-api-services-storage:jar
+ com.google.auth:google-auth-library-credentials:jar
+ com.google.auth:google-auth-library-oauth2-http:jar
+ com.google.cloud:google-cloud-core:jar
+ com.google.cloud:google-cloud-core-http:jar
+ com.google.cloud:google-cloud-monitoring:jar
+ com.google.http-client:google-http-client-gson:jar
+ org.apache.httpcomponents:httpclient:jar
+ org.apache.httpcomponents:httpcore:jar
+ com.google.j2objc:j2objc-annotations:jar
+ com.fasterxml.jackson.core::jar
+ com.fasterxml.jackson.dataformat::jar
+ com.fasterxml.jackson.datatype::jar
+ com.fasterxml.jackson::jar
+ com.github.stephenc.jcip:jcip-annotations:jar
+ net.java.dev.jna::jar
+ com.google.code.findbugs:jsr305:jar
+ com.squareup.okhttp3:okhttp:jar
+ com.squareup.okio::jar
+ io.opentelemetry::jar
+ com.google.cloud.opentelemetry:shared-resourcemapping:jar
+ org.codehaus.woodstox:stax2-api:jar
+ org.threeten::jar
+ org.lz4:lz4-java:jar
+ io.opentelemetry.contrib::jar
+ io.opentelemetry.semconv::jar
+ io.perfmark:perfmark-api:jar
+ com.google.re2j:re2j:jar
+ org.reactivestreams:reactive-streams:jar
+ io.projectreactor:reactor-core:jar
+ org.bouncycastle::jar
+ com.microsoft.azure:msal4j:jar
+ com.microsoft.azure:msal4j-persistence-extension:jar
+ com.amazonaws:aws-java-sdk-core:jar
+ com.amazonaws:aws-java-sdk-kms:jar
+ com.amazonaws:aws-java-sdk-s3:jar
+ com.nimbusds:oauth2-oidc-sdk:jar
+ com.azure:azure-core:jar
+ com.azure:azure-identity:jar
+ com.azure:azure-core-http-netty:jar
+ com.azure:azure-json:jar
+ com.microsoft.azure:azure-keyvault-core:jar
+ com.azure:azure-security-keyvault-secrets:jar
+ com.azure:azure-storage-blob:jar
+ com.azure:azure-storage-common:jar
+ com.azure:azure-storage-internal-avro:jar
+ com.azure:azure-xml:jar
+ com.nimbusds:nimbus-jose-jwt:jar
+ io.confluent::jar
+ com.google.code.gson:gson:jar
+ org.apache.commons:commons-collections4:jar
+ org.jspecify:jspecify:jar
+ org.apache.commons:commons-lang3:jar
+ commons-collections:commons-collections:jar
+ com.fasterxml.woodstox:woodstox-core:jar
+ com.ibm.icu:icu4j:jar
+ com.fasterxml.jackson.core:jackson-annotations:jar
+ software.amazon.awssdk:*:jar
+ software.amazon.eventstream:eventstream:jar
+ javax.annotation:javax.annotation-api:jar
+ javax.inject:javax.inject:jar
+ org.jctools:jctools-core:jar
+ com.nimbusds:lang-tag:jar
+ io.dropwizard.metrics:*:jar
+ com.eclipsesource.minimal-json:minimal-json:jar
+ org.slf4j:slf4j-api:jar
+
+ org.apache.spark:spark-core_2.12:jar
+ org.apache.spark:spark-streaming_2.12:jar
+ org.apache.spark:spark-launcher_2.12:jar
+ org.apache.spark:spark-kvstore_2.12:jar
+ org.apache.spark:spark-network-common_2.12:jar
+ org.apache.spark:spark-network-shuffle_2.12:jar
+ org.apache.spark:spark-unsafe_2.12:jar
+ org.apache.spark:spark-tags_2.12:jar
+
+ plugins/engines/beam/lib-beam
+
+
+
diff --git a/plugins/engines/beam/src/assembly/assembly-libs.xml b/plugins/engines/beam/src/assembly/assembly-libs.xml
new file mode 100644
index 00000000000..4736424a1ca
--- /dev/null
+++ b/plugins/engines/beam/src/assembly/assembly-libs.xml
@@ -0,0 +1,161 @@
+
+
+
+
+ libs
+
+ zip
+
+ .
+
+
+
+
+
+
+ runtime
+
+ org.apache.spark:spark-core_2.12:jar
+ org.apache.spark:spark-streaming_2.12:jar
+ org.apache.spark:spark-launcher_2.12:jar
+ org.apache.spark:spark-kvstore_2.12:jar
+ org.apache.spark:spark-network-common_2.12:jar
+ org.apache.spark:spark-network-shuffle_2.12:jar
+ org.apache.spark:spark-unsafe_2.12:jar
+ org.apache.spark:spark-tags_2.12:jar
+
+ lib/spark-client
+
+
+ runtime
+
+ org.apache.hadoop:*:jar
+ org.apache.hadoop.thirdparty:*:jar
+ org.yaml:snakeyaml:jar
+ org.jetbrains:annotations:jar
+ com.google.auto.value:auto-value-annotations:jar
+ org.apache.beam:beam-vendor-guava-32_1_2-jre:jar
+ com.github.luben:zstd-jni:jar
+ com.google.auth:google-auth-library-oauth2-http:jar
+ com.google.oauth-client:google-oauth-client:jar
+ com.google.api-client:google-api-client:jar
+ com.google.http-client:google-http-client:jar
+ com.google.http-client:google-http-client-apache-v2:jar
+ com.google.http-client:google-http-client-appengine:jar
+ google-http-client-gson:jar
+ com.google.http-client:google-http-client-jackson2:jar
+ com.google.android:annotations:jar
+ org.codehaus.mojo:animal-sniffer-annotations:jar
+ com.google.api:api-common:jar
+ com.google.auto.value:auto-value-annotations:jar
+ org.jetbrains.kotlin::jar
+ io.netty::jar
+ io.projectreactor.netty::jar
+ io.projectreactor:reactor-core:jar
+ io.grpc:grpc-netty-shaded:jar
+ org.apache.kafka::jar
+ com.google.cloud:google-cloud-storage:jar
+ io.grpc::jar
+ com.google.cloud:google-cloud-core-grpc:jar
+ com.google.api.grpc:grpc-google-cloud-storage-v2:jar
+ com.google.api:gax-grpc:jar
+ com.google.api:gax:jar
+ com.google.api.grpc::jar
+ org.checkerframework:checker-qual:jar
+ io.confluent:common-utils:jar
+ commons-cli:commons-cli:jar
+ commons-codec:commons-codec:jar
+ commons-logging:commons-logging:jar
+ org.apache.commons:commons-text:jar
+ com.google.cloud.opentelemetry:detector-resources-support:jar
+ com.google.errorprone:error_prone_annotations:jar
+ com.google.cloud.opentelemetry:exporter-metrics:jar
+ com.google.api:gax-httpjson:jar
+ com.google.apis:google-api-services-storage:jar
+ com.google.auth:google-auth-library-credentials:jar
+ com.google.auth:google-auth-library-oauth2-http:jar
+ com.google.cloud:google-cloud-core:jar
+ com.google.cloud:google-cloud-core-http:jar
+ com.google.cloud:google-cloud-monitoring:jar
+ com.google.http-client:google-http-client-gson:jar
+ org.apache.httpcomponents:httpclient:jar
+ org.apache.httpcomponents:httpcore:jar
+ com.google.j2objc:j2objc-annotations:jar
+ com.fasterxml.jackson.core::jar
+ com.fasterxml.jackson.dataformat::jar
+ com.fasterxml.jackson.datatype::jar
+ com.fasterxml.jackson::jar
+ com.github.stephenc.jcip:jcip-annotations:jar
+ net.java.dev.jna::jar
+ com.google.code.findbugs:jsr305:jar
+ com.squareup.okhttp3:okhttp:jar
+ com.squareup.okio::jar
+ io.opentelemetry::jar
+ io.opentelemetry.contrib::jar
+ io.opentelemetry.semconv::jar
+ com.google.cloud.opentelemetry:shared-resourcemapping:jar
+ org.codehaus.woodstox:stax2-api:jar
+ org.threeten::jar
+ org.lz4:lz4-java:jar
+ io.perfmark:perfmark-api:jar
+ com.google.re2j:re2j:jar
+ org.reactivestreams:reactive-streams:jar
+ org.bouncycastle::jar
+ com.microsoft.azure:msal4j:jar
+ com.microsoft.azure:msal4j-persistence-extension:jar
+ com.amazonaws:aws-java-sdk-core:jar
+ com.amazonaws:aws-java-sdk-kms:jar
+ com.amazonaws:aws-java-sdk-s3:jar
+ com.nimbusds:oauth2-oidc-sdk:jar
+ com.azure:azure-core:jar
+ com.azure:azure-identity:jar
+ com.azure:azure-core-http-netty:jar
+ com.azure:azure-json:jar
+ com.microsoft.azure:azure-keyvault-core:jar
+ com.azure:azure-security-keyvault-secrets:jar
+ com.azure:azure-storage-blob:jar
+ com.azure:azure-storage-common:jar
+ com.azure:azure-storage-internal-avro:jar
+ com.azure:azure-xml:jar
+ com.nimbusds:nimbus-jose-jwt:jar
+ io.confluent::jar
+ com.google.code.gson:gson:jar
+ org.apache.commons:commons-collections4:jar
+ org.apache.commons:commons-lang3:jar
+ commons-collections:commons-collections:jar
+ com.fasterxml.woodstox:woodstox-core:jar
+ com.ibm.icu:icu4j:jar
+ com.fasterxml.jackson.core:jackson-annotations:jar
+ software.amazon.awssdk:*:jar
+ software.amazon.eventstream:eventstream:jar
+ javax.annotation:javax.annotation-api:jar
+ javax.inject:javax.inject:jar
+ org.jctools:jctools-core:jar
+ com.nimbusds:lang-tag:jar
+ io.dropwizard.metrics:*:jar
+ com.eclipsesource.minimal-json:minimal-json:jar
+ org.slf4j:slf4j-api:jar
+
+ lib/core
+
+
+
\ No newline at end of file
diff --git a/plugins/engines/beam/src/assembly/assembly.xml b/plugins/engines/beam/src/assembly/assembly.xml
index c012216d5cb..fe175665fb7 100644
--- a/plugins/engines/beam/src/assembly/assembly.xml
+++ b/plugins/engines/beam/src/assembly/assembly.xml
@@ -18,7 +18,8 @@
- hop-engines-beam
+
+ pluginzip
@@ -50,265 +51,9 @@
plugins/engines/beam
-
- runtime
-
- org.apache.hop:hop-engines-beam:jar
- org.apache.hadoop:*:jar
- org.apache.hadoop.thirdparty:*:jar
- org.yaml:snakeyaml:jar
- com.google.protobuf:protobuf-java:jar
- com.google.protobuf:protobuf-java-util:jar
- org.jetbrains:annotations:jar
- com.google.auto.value:auto-value-annotations:jar
- org.apache.beam:beam-vendor-guava-32_1_2-jre:jar
- com.github.luben:zstd-jni:jar
- com.google.auth:google-auth-library-oauth2-http:jar
- com.google.oauth-client:google-oauth-client:jar
- com.google.api-client:google-api-client:jar
- com.google.http-client:google-http-client:jar
- com.google.http-client:google-http-client-apache-v2:jar
- com.google.http-client:google-http-client-appengine:jar
- google-http-client-gson:jar
- com.google.http-client:google-http-client-jackson2:jar
- com.google.android:annotations:jar
- org.codehaus.mojo:animal-sniffer-annotations:jar
- com.google.api:api-common:jar
- com.google.auto.value:auto-value-annotations:jar
- com.google.guava::jar
- org.jetbrains.kotlin::jar
- io.netty::jar
- org.codehaus.mojo:animal-sniffer-annotations:jar
- io.projectreactor.netty::jar
- io.grpc:grpc-netty-shaded:jar
- io.grpc:grpc-xds:jar
- org.apache.kafka::jar
- org.xerial.snappy:snappy-java:jar
- org.eclipse.jetty::jar
- org.eclipse.jetty.compression::jar
- com.google.cloud:google-cloud-storage:jar
- io.grpc::jar
- com.google.cloud:google-cloud-core-grpc:jar
- com.google.api.grpc:grpc-google-cloud-storage-v2:jar
- com.google.api:gax-grpc:jar
- com.google.api.grpc::jar
- org.checkerframework:checker-qual:jar
- io.confluent:common-utils:jar
- commons-cli:commons-cli:jar
- commons-codec:commons-codec:jar
- commons-logging:commons-logging:jar
- org.apache.commons:commons-text:jar
- com.google.cloud.opentelemetry:detector-resources-support:jar
- com.google.errorprone:error_prone_annotations:jar
- com.google.cloud.opentelemetry:exporter-metrics:jar
- com.google.api:gax:jar
- com.google.api:gax-httpjson:jar
- com.google.apis:google-api-services-storage:jar
- com.google.auth:google-auth-library-credentials:jar
- com.google.auth:google-auth-library-oauth2-http:jar
- com.google.cloud:google-cloud-core:jar
- com.google.cloud:google-cloud-core-http:jar
- com.google.cloud:google-cloud-monitoring:jar
- com.google.http-client:google-http-client-gson:jar
- org.apache.httpcomponents:httpclient:jar
- org.apache.httpcomponents:httpcore:jar
- com.google.j2objc:j2objc-annotations:jar
- com.fasterxml.jackson.core::jar
- com.fasterxml.jackson.dataformat::jar
- com.fasterxml.jackson.datatype::jar
- com.fasterxml.jackson::jar
- com.github.stephenc.jcip:jcip-annotations:jar
- net.java.dev.jna::jar
- com.google.code.findbugs:jsr305:jar
- com.squareup.okhttp3:okhttp:jar
- com.squareup.okio::jar
- io.opentelemetry::jar
- com.google.cloud.opentelemetry:shared-resourcemapping:jar
- org.codehaus.woodstox:stax2-api:jar
- org.threeten::jar
- org.lz4:lz4-java:jar
- io.opentelemetry.contrib::jar
- io.opentelemetry.semconv::jar
- io.perfmark:perfmark-api:jar
- com.google.re2j:re2j:jar
- org.reactivestreams:reactive-streams:jar
- io.projectreactor:reactor-core:jar
- org.bouncycastle::jar
- com.microsoft.azure:msal4j:jar
- com.microsoft.azure:msal4j-persistence-extension:jar
- com.amazonaws:aws-java-sdk-core:jar
- com.amazonaws:aws-java-sdk-kms:jar
- com.amazonaws:aws-java-sdk-s3:jar
- com.nimbusds:oauth2-oidc-sdk:jar
- com.azure:azure-core:jar
- com.azure:azure-identity:jar
- com.azure:azure-core-http-netty:jar
- com.azure:azure-json:jar
- com.microsoft.azure:azure-keyvault-core:jar
- com.azure:azure-security-keyvault-secrets:jar
- com.azure:azure-storage-blob:jar
- com.azure:azure-storage-common:jar
- com.azure:azure-storage-internal-avro:jar
- com.azure:azure-xml:jar
- com.nimbusds:nimbus-jose-jwt:jar
- io.confluent::jar
- com.google.code.gson:gson:jar
- org.apache.commons:commons-collections4:jar
- org.jspecify:jspecify:jar
- org.apache.commons:commons-lang3:jar
- commons-collections:commons-collections:jar
- com.fasterxml.woodstox:woodstox-core:jar
- com.ibm.icu:icu4j:jar
- com.fasterxml.jackson.core:jackson-annotations:jar
- software.amazon.awssdk:*:jar
- software.amazon.eventstream:eventstream:jar
- javax.annotation:javax.annotation-api:jar
- javax.inject:javax.inject:jar
- org.jctools:jctools-core:jar
- com.nimbusds:lang-tag:jar
- io.dropwizard.metrics:*:jar
- com.eclipsesource.minimal-json:minimal-json:jar
- org.slf4j:slf4j-api:jar
-
- org.apache.spark:spark-core_2.12:jar
- org.apache.spark:spark-streaming_2.12:jar
- org.apache.spark:spark-launcher_2.12:jar
- org.apache.spark:spark-kvstore_2.12:jar
- org.apache.spark:spark-network-common_2.12:jar
- org.apache.spark:spark-network-shuffle_2.12:jar
- org.apache.spark:spark-unsafe_2.12:jar
- org.apache.spark:spark-tags_2.12:jar
-
- lib/beam
-
-
-
- runtime
-
- org.apache.spark:spark-core_2.12:jar
- org.apache.spark:spark-streaming_2.12:jar
- org.apache.spark:spark-launcher_2.12:jar
- org.apache.spark:spark-kvstore_2.12:jar
- org.apache.spark:spark-network-common_2.12:jar
- org.apache.spark:spark-network-shuffle_2.12:jar
- org.apache.spark:spark-unsafe_2.12:jar
- org.apache.spark:spark-tags_2.12:jar
-
- lib/spark-client
-
-
- runtime
-
- org.apache.hadoop:*:jar
- org.apache.hadoop.thirdparty:*:jar
- org.yaml:snakeyaml:jar
- org.jetbrains:annotations:jar
- com.google.auto.value:auto-value-annotations:jar
- org.apache.beam:beam-vendor-guava-32_1_2-jre:jar
- com.github.luben:zstd-jni:jar
- com.google.auth:google-auth-library-oauth2-http:jar
- com.google.oauth-client:google-oauth-client:jar
- com.google.api-client:google-api-client:jar
- com.google.http-client:google-http-client:jar
- com.google.http-client:google-http-client-apache-v2:jar
- com.google.http-client:google-http-client-appengine:jar
- google-http-client-gson:jar
- com.google.http-client:google-http-client-jackson2:jar
- com.google.android:annotations:jar
- org.codehaus.mojo:animal-sniffer-annotations:jar
- com.google.api:api-common:jar
- com.google.auto.value:auto-value-annotations:jar
- org.jetbrains.kotlin::jar
- io.netty::jar
- io.projectreactor.netty::jar
- io.projectreactor:reactor-core:jar
- io.grpc:grpc-netty-shaded:jar
- org.apache.kafka::jar
- com.google.cloud:google-cloud-storage:jar
- io.grpc::jar
- com.google.cloud:google-cloud-core-grpc:jar
- com.google.api.grpc:grpc-google-cloud-storage-v2:jar
- com.google.api:gax-grpc:jar
- com.google.api:gax:jar
- com.google.api.grpc::jar
- org.checkerframework:checker-qual:jar
- io.confluent:common-utils:jar
- commons-cli:commons-cli:jar
- commons-codec:commons-codec:jar
- commons-logging:commons-logging:jar
- org.apache.commons:commons-text:jar
- com.google.cloud.opentelemetry:detector-resources-support:jar
- com.google.errorprone:error_prone_annotations:jar
- com.google.cloud.opentelemetry:exporter-metrics:jar
- com.google.api:gax-httpjson:jar
- com.google.apis:google-api-services-storage:jar
- com.google.auth:google-auth-library-credentials:jar
- com.google.auth:google-auth-library-oauth2-http:jar
- com.google.cloud:google-cloud-core:jar
- com.google.cloud:google-cloud-core-http:jar
- com.google.cloud:google-cloud-monitoring:jar
- com.google.http-client:google-http-client-gson:jar
- org.apache.httpcomponents:httpclient:jar
- org.apache.httpcomponents:httpcore:jar
- com.google.j2objc:j2objc-annotations:jar
- com.fasterxml.jackson.core::jar
- com.fasterxml.jackson.dataformat::jar
- com.fasterxml.jackson.datatype::jar
- com.fasterxml.jackson::jar
- com.github.stephenc.jcip:jcip-annotations:jar
- net.java.dev.jna::jar
- com.google.code.findbugs:jsr305:jar
- com.squareup.okhttp3:okhttp:jar
- com.squareup.okio::jar
- io.opentelemetry::jar
- io.opentelemetry.contrib::jar
- io.opentelemetry.semconv::jar
- com.google.cloud.opentelemetry:shared-resourcemapping:jar
- org.codehaus.woodstox:stax2-api:jar
- org.threeten::jar
- org.lz4:lz4-java:jar
- io.perfmark:perfmark-api:jar
- com.google.re2j:re2j:jar
- org.reactivestreams:reactive-streams:jar
- org.bouncycastle::jar
- com.microsoft.azure:msal4j:jar
- com.microsoft.azure:msal4j-persistence-extension:jar
- com.amazonaws:aws-java-sdk-core:jar
- com.amazonaws:aws-java-sdk-kms:jar
- com.amazonaws:aws-java-sdk-s3:jar
- com.nimbusds:oauth2-oidc-sdk:jar
- com.azure:azure-core:jar
- com.azure:azure-identity:jar
- com.azure:azure-core-http-netty:jar
- com.azure:azure-json:jar
- com.microsoft.azure:azure-keyvault-core:jar
- com.azure:azure-security-keyvault-secrets:jar
- com.azure:azure-storage-blob:jar
- com.azure:azure-storage-common:jar
- com.azure:azure-storage-internal-avro:jar
- com.azure:azure-xml:jar
- com.nimbusds:nimbus-jose-jwt:jar
- io.confluent::jar
- com.google.code.gson:gson:jar
- org.apache.commons:commons-collections4:jar
- org.apache.commons:commons-lang3:jar
- commons-collections:commons-collections:jar
- com.fasterxml.woodstox:woodstox-core:jar
- com.ibm.icu:icu4j:jar
- com.fasterxml.jackson.core:jackson-annotations:jar
- software.amazon.awssdk:*:jar
- software.amazon.eventstream:eventstream:jar
- javax.annotation:javax.annotation-api:jar
- javax.inject:javax.inject:jar
- org.jctools:jctools-core:jar
- com.nimbusds:lang-tag:jar
- io.dropwizard.metrics:*:jar
- com.eclipsesource.minimal-json:minimal-json:jar
- org.slf4j:slf4j-api:jar
-
- lib/core
-
-
\ No newline at end of file
+
+
+ ${project.basedir}/src/assembly/assembly-beam-runtime.xml
+
+
diff --git a/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/HopBeamGuiPlugin.java b/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/HopBeamGuiPlugin.java
index c6119d456e6..6801d5eb756 100644
--- a/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/HopBeamGuiPlugin.java
+++ b/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/HopBeamGuiPlugin.java
@@ -459,8 +459,9 @@ public static File resolveSparkClientPackDir(String sparkClientVersion) {
}
/**
- * @param versionedPack when true, also skip {@code spark-*.jar} under {@code lib/beam} so Beam's
- * fixed Spark fragments cannot mix with an alternate client pack
+ * @param versionedPack when true, also skip {@code spark-*.jar} under Beam SDK folders ({@code
+ * lib/beam} legacy or {@code plugins/engines/beam/lib-beam}) so Beam's fixed Spark fragments
+ * cannot mix with an alternate client pack
*/
private static void collectJarsExcludingSparkClientPacks(
File dir, Set jarFiles, boolean versionedPack) {
@@ -483,11 +484,12 @@ private static void collectJarsExcludingSparkClientPacks(
if (child.isDirectory()) {
collectJarsExcludingSparkClientPacks(child, jarFiles, versionedPack);
} else if (child.isFile() && child.getName().endsWith(".jar")) {
- // Versioned packs own all spark-* jars; drop Beam's spark fragments from lib/beam
+ // Versioned packs own all spark-* jars; drop Beam's spark fragments from lib-beam /
+ // lib/beam
if (versionedPack
&& child.getName().startsWith("spark-")
&& parent != null
- && "beam".equals(parent.getName())) {
+ && isBeamSdkJarFolder(parent)) {
continue;
}
jarFiles.add(child);
@@ -495,6 +497,19 @@ private static void collectJarsExcludingSparkClientPacks(
}
}
+ /** True for legacy {@code lib/beam} or marketplace {@code …/beam/lib-beam}. */
+ static boolean isBeamSdkJarFolder(File folder) {
+ if (folder == null) {
+ return false;
+ }
+ String name = folder.getName();
+ if ("lib-beam".equals(name)) {
+ return true;
+ }
+ File parent = folder.getParentFile();
+ return "beam".equals(name) && parent != null && "lib".equals(parent.getName());
+ }
+
/**
* Add a toolbar item just above the pipeline graph to quickly visit the execution of a Dataflow
* pipeline in the GCP console.
diff --git a/plugins/misc/marketplace/README.md b/plugins/misc/marketplace/README.md
new file mode 100644
index 00000000000..101148c6feb
--- /dev/null
+++ b/plugins/misc/marketplace/README.md
@@ -0,0 +1,494 @@
+
+
+# Hop Marketplace plugin
+
+Bundled core plugin that installs optional Hop plugins from Maven repositories.
+**Default primary:** [Apache Repository](https://repository.apache.org/content/groups/public/)
+(`repository.apache.org`), with **Maven Central** as fallback.
+
+## CLI
+
+From the Hop install directory (or invoke the `hop` script by path — it already
+`cd`s to the install root):
+
+```bash
+./hop marketplace install hop-tech-parquet
+./hop marketplace install hop-engines-spark:2.19.0 --repo asf
+./hop marketplace install hop-engines-beam
+./hop marketplace install datavault # short name → discovered GAV + version
+./hop marketplace list
+./hop marketplace query parquet
+./hop marketplace query engines
+./hop marketplace uninstall hop-tech-parquet
+
+# Repositories (saved to ${HOP_CONFIG_FOLDER}/hop-config.json)
+./hop marketplace repo list
+./hop marketplace repo add --id local-nexus --url http://127.0.0.1:8081/repository/hop-plugins/ --primary
+./hop marketplace repo add --id community --url https://nexus.example/repository/hop/ --browse
+./hop marketplace repo import hop-marketplace-repo.yaml
+./hop marketplace repo export community -o community-marketplace.yaml
+./hop marketplace repo set-primary asf
+./hop marketplace repo reset-defaults
+
+# Discovery: Apache optional catalog + every browse=true repo (live Nexus zip list)
+# Default: ASCII table. --csv for scripts. --include-gav adds groupId:artifactId:version.
+./hop marketplace query parquet
+./hop marketplace query datavault
+./hop marketplace query vault --repo community
+./hop marketplace query datavault --include-gav
+./hop marketplace query --csv > plugins.csv
+
+# Declarative environment (CI/CD / Docker)
+./hop marketplace apply -f hop-env.yaml
+./hop marketplace apply -f hop-env.yaml --prune
+./hop marketplace validate -f hop-env.yaml
+
+# Restore all optional plugins that used to ship in the fat client
+./hop marketplace apply -f full-client-env.yaml
+```
+
+Install tries the **primary** repository first, then other **enabled** repositories
+in list order, until the zip is found (or all fail). Use `--repo ` to force a
+single repository.
+
+Coordinates:
+
+- `artifactId` (uses config `defaultVersion` / Hop version)
+- `artifactId:version`
+- `groupId:artifactId:version`
+
+After install or uninstall, **restart Hop** so the plugin registry reloads.
+
+### hop-env.yaml
+
+See `config/projects/samples/marketplace/hop-env.example.yaml` (after install) or
+`plugins/misc/marketplace/src/main/samples/hop-env.example.yaml` in source.
+
+```yaml
+version: "1.0"
+hopVersion: "2.19.0"
+enforceOnRun: false # set true to hard-fail hop-run on drift
+repositories:
+ - id: asf
+ url: "https://repository.apache.org/content/groups/public/"
+ - id: central
+ url: "https://repo1.maven.org/maven2/"
+plugins:
+ - artifactId: hop-tech-parquet
+ version: "2.19.0"
+dependencies:
+ - groupId: org.postgresql
+ artifactId: postgresql
+ version: "42.7.3"
+```
+
+`hop-run` checks the env file only when `enforceOnRun: true` or `-Dhop.env.enforce=true`.
+Discovery order: `-f` / `HOP_ENV_FILE` / `PROJECT_HOME/hop-env.yaml` / install-root `hop-env.yaml`.
+
+### Projects lifecycle environments
+
+When the **Projects** plugin enables a lifecycle environment, Marketplace can
+check the same hop-env file using settings stored as **namespaced attributes**
+on that environment (`IAttributes` group `marketplace`). No compile-time
+dependency exists between the two plugins; they share core `AttributesContext`.
+
+| Attribute key (`marketplace` group) | Values | Meaning |
+|-------------------------------------|--------|---------|
+| `envFile` | path (variables OK) | hop-env / full-client-env file |
+| `onEnable` | `off` / `warn` / `enforce` | Action when the environment is enabled |
+| `strict` | `true` / `false` | Extra marketplace plugins count as drift |
+| `autoApply` | `true` / `false` | Apply missing plugins on enable (default off) |
+
+If `onEnable` is unset, purpose defaults apply: **Production** → `enforce`,
+**Testing/Acceptance** → `warn`, **Development/CI/CB** → `off`.
+
+Configure these on the environment dialog **Marketplace plugins** tab (only
+visible when this plugin is installed). At enable time extension point
+`HopProjectEnvironmentAfterEnabled` runs the check.
+
+## Config (`hop-config.json`)
+
+Marketplace settings live under the `marketplace` key in **`${HOP_CONFIG_FOLDER}/hop-config.json`**
+(always that path). If `HOP_CONFIG_FOLDER` is unset, Hop defaults it to
+`/config` (with the hop launcher: `/config`).
+
+When the `marketplace` key is absent, defaults are:
+
+1. **asf** (primary) — `https://repository.apache.org/content/groups/public/`
+2. **central** — `https://repo1.maven.org/maven2/`
+
+```json
+{
+ "marketplace": {
+ "enabled": true,
+ "groupId": "org.apache.hop",
+ "defaultVersion": "2.19.0-SNAPSHOT",
+ "repositories": [
+ {
+ "id": "asf",
+ "name": "Apache Repository",
+ "url": "https://repository.apache.org/content/groups/public/",
+ "primary": true,
+ "enabled": true
+ },
+ {
+ "id": "central",
+ "name": "Maven Central",
+ "url": "https://repo1.maven.org/maven2/",
+ "primary": false,
+ "enabled": true
+ }
+ ]
+ }
+}
+```
+
+Local Sonatype Nexus for development (see `docker/marketplace-nexus/README.md`):
+
+```bash
+./hop marketplace repo add --id local-nexus \
+ --url http://127.0.0.1:8081/repository/hop-plugins/ --primary
+./hop marketplace install hop-tech-parquet
+```
+
+Anonymous local Nexus needs **no** username/password and **no** `HOP_MARKETPLACE_*`
+env (wrong Basic auth causes HTTP 401 even when anonymous would work).
+
+Corporate private repos can use Basic auth (prefer a read-only user):
+
+```bash
+export HOP_MARKETPLACE_USERNAME=reader
+export HOP_MARKETPLACE_PASSWORD='…'
+```
+
+## GUI
+
+Hop GUI → **Tools → Marketplace…**, or the main toolbar icon after **Save As…**
+(separator before it), opens the marketplace. The startup **Welcome** dialog also has a
+**Marketplace** topic (docs link, open dialog, install-all via `full-client-env.yaml`).
+The **Plugins** tab lists Apache optionals plus a live zip list from every enabled
+repository with **Browse** turned on (same as `marketplace query`). The **Repositories**
+tab only manages endpoints (add/edit, Import/Export, primary, browse flag) — it does not
+list plugins. Install uses the primary repository first, then fallbacks.
+
+## Company marketplace (shareable repo definition)
+
+Three steps:
+
+```bash
+./hop marketplace repo import hop-marketplace-repo.yaml # browse: true
+./hop marketplace query datavault # no special flags
+./hop marketplace install datavault # resolves hop-datavault + version from repo
+# or explicit: ./hop marketplace install org.apache.hop:hop-datavault:0.4.0-SNAPSHOT
+```
+
+
+**How listing works**
+
+- Bundled Apache optional plugins: **version = running Hop version** (not absolute latest on ASF)
+- Every enabled repo with **`browse: true`**: live Nexus search for `*.zip` (one row per artifact, latest plugin version, last updated)
+- Optional YAML `plugins:` metadata **enriches** names/categories and can set:
+ - `minHopVersion` / `maxHopVersion` — hide if this Hop is outside the range (e.g. datavault needs ≥ 2.18.1)
+ - `version` — optional pin of the plugin artifact; otherwise latest from browse
+
+GUI **Plugins** tab uses the same discovery as `query`.
+
+Sample: `src/main/samples/hop-marketplace-repo.community.example.yaml`
+
+**Environment file** path + **Browse…** / **Edit…** / **Validate** / **Apply** manage
+declarative `hop-env.yaml` files. **Edit…** opens a tabbed editor (General,
+Repositories, Plugins, Dependencies) with New/Open/Save/Save As, catalog multi-add,
+and optional import of repositories from hop-config.
+
+Restart after install, apply, or uninstall.
+
+## Optional plugin registry (source of truth)
+
+All marketplace-optional plugins are declared in **one file only**:
+
+```text
+plugins/misc/marketplace/src/main/resources/org/apache/hop/marketplace/optional-plugins.yaml
+```
+
+Do not hardcode plugin lists elsewhere. Everything below reads this registry:
+
+| Consumer | How it uses the registry |
+|----------|--------------------------|
+| GUI catalog / `hop marketplace query` | Loads YAML at runtime |
+| `full-client-env.yaml` | Generated by `tools/generate-full-client-env.sh` (Maven `generate-resources`) |
+| Local publish to Nexus/Artifactory | `docker/marketplace-nexus/publish-marketplace-plugins.sh` |
+| Unpack zips into an install (IT images) | `tools/install-wave1-plugins.sh` via `tools/list-marketplace-plugins.sh` |
+
+Each entry needs at least:
+
+```yaml
+ - artifactId: hop-tech-parquet
+ name: Parquet
+ category: Technology
+ description: Parquet file format support.
+ installPath: plugins/tech/parquet # path under Hop home after install
+ modulePath: plugins/tech/parquet # Maven module in this repo (for package/publish)
+```
+
+Top-level `groupId` (default `org.apache.hop`) is used when publishing.
+
+### Making a plugin optional (checklist)
+
+1. Exclude it from the fat hop-client assembly.
+2. **Add one entry** to `optional-plugins.yaml` (`artifactId`, `installPath`, `modulePath`, …).
+3. Rebuild marketplace / client so `full-client-env.yaml` regenerates.
+4. For local testing: package + publish the zip (see next section), point Hop at that repo,
+ then `./hop marketplace apply -f full-client-env.yaml` (or install a single GAV).
+5. Restart Hop.
+
+## Publishing plugin zips (local Nexus / Artifactory)
+
+Marketplace installs **Maven GAV zips**, not jars alone. Layout:
+
+```text
+{repoBase}/org/apache/hop/{artifactId}/{version}/{artifactId}-{version}.zip
+```
+
+Plugin modules already produce that zip via the reactor assembly
+(`appendAssemblyId=false` → `modulePath/target/{artifactId}-{version}.zip`).
+
+### Script: `publish-marketplace-plugins.sh`
+
+From the **Hop repo root**:
+
+```bash
+# List what the registry will publish (no network)
+./tools/list-marketplace-plugins.sh
+./tools/list-marketplace-plugins.sh --zips
+```
+
+#### Local Sonatype Nexus (Docker)
+
+```bash
+./docker/marketplace-nexus/start.sh
+# UI http://localhost:8081/ — admin / hop-nexus-dev
+# Repo http://127.0.0.1:8081/repository/hop-plugins/
+
+export NEXUS_PASSWORD=hop-nexus-dev
+
+# Build every registry modulePath, then deploy all zips:
+./docker/marketplace-nexus/publish-marketplace-plugins.sh --package
+
+# Or deploy only zips that already exist under target/:
+./docker/marketplace-nexus/publish-marketplace-plugins.sh
+
+# Preview without uploading:
+./docker/marketplace-nexus/publish-marketplace-plugins.sh --dry-run
+```
+
+Prefer **`127.0.0.1`** over `localhost` in Hop config (IPv6 vs Docker IPv4).
+
+More detail: `docker/marketplace-nexus/README.md`.
+
+#### Your Artifactory (or any Maven 2 repo)
+
+```bash
+export ARTIFACTORY_URL='https://artifactory.example.com/artifactory/hop-plugins-local'
+export ARTIFACTORY_USER='deploy-user'
+export ARTIFACTORY_PASSWORD='…'
+export NEXUS_REPO_ID=artifactory # must match used for deploy auth
+export HOP_VERSION=2.19.0-SNAPSHOT # optional; default 2.19.0-SNAPSHOT
+
+./docker/marketplace-nexus/publish-marketplace-plugins.sh --package
+```
+
+Equivalent env names also accepted: `NEXUS_REPO_URL`, `NEXUS_USER`, `NEXUS_PASSWORD`,
+`NEXUS_ADMIN_PASSWORD`.
+
+| Flag / env | Meaning |
+|------------|---------|
+| `--package` | `mvn -pl -am package -DskipTests`, then deploy |
+| `--dry-run` | Print deploy targets; no Maven deploy (and no password required) |
+| `HOP_VERSION` | Version coordinate and zip filename (default `2.19.0-SNAPSHOT`) |
+| `GROUP_ID` | Override group (default: `groupId` from the YAML, else `org.apache.hop`) |
+| `NEXUS_REPO_ID` | Maven `settings.xml` server id for credentials (default `hop-plugins`) |
+
+Missing zips are **skipped** with a message (`SKIP (not built)`), not a hard failure—so you
+can publish a subset after a partial package.
+
+`publish-wave1-plugins.sh` is a **deprecated alias** of `publish-marketplace-plugins.sh`.
+
+### Point Hop at the local repo and install
+
+```bash
+cd /path/to/hop-client # install root (hop launcher cd's here)
+
+./hop marketplace repo add --id local-nexus \
+ --url http://127.0.0.1:8081/repository/hop-plugins/ --primary
+
+./hop marketplace install hop-tech-parquet
+# or restore everything listed in the registry / full-client env:
+./hop marketplace apply -f full-client-env.yaml
+```
+
+Anonymous **read** on the repo is ideal for installs. Deploy credentials stay only on the
+publish side (admin / CI user).
+
+### Related helper scripts
+
+```bash
+# artifactId|modulePath (or --zips / --modules)
+./tools/list-marketplace-plugins.sh
+
+# Unzip all built registry zips into a Hop install (no Maven repo)
+./tools/install-wave1-plugins.sh [HOP_INSTALL_DIR]
+
+# Regenerate full-client-env.yaml (also run by marketplace module generate-resources)
+./tools/generate-full-client-env.sh [HOP_VERSION] [OUTPUT_PATH]
+```
+
+## Packaging & size
+
+Optional plugins are **not** bundled in the default hop-client zip so the download stays
+under the ASF packaging limit.
+
+### Production / ASF Nexus
+
+Release `release:perform` uses `-P=-assemblies -DskipTests` (root `pom.xml`):
+
+- Skips the `assemblies/` reactor (hop-client goes to dist.apache.org).
+- Keeps the per-module **`assembly`** profile so each plugin zip is attached and
+ staged to `repository.apache.org`, then Maven Central after the vote.
+
+Example after release:
+
+```text
+https://repository.apache.org/content/groups/public/org/apache/hop/hop-tech-parquet//hop-tech-parquet-.zip
+```
+
+### SNAPSHOT (CI)
+
+Jenkins on **main** does two steps (see `Jenkinsfile`):
+
+1. `mvn … -DaltDeploymentRepository=…::file:./local-snapshots-dir clean deploy`
+ (full reactor: tests, assemblies, plugin zips)
+2. `mvn -P deploy-snapshots wagon:upload` → ASF snapshots
+ (`repository.apache.org`). Plugin zips are **included**; hop-assemblies and
+ core/engine/ui zips stay excluded.
+
+**Pre-merge confidence (does not need ASF credentials):**
+
+```bash
+# 1) Same deploy *layout* as Jenkins step 1 (skipTests optional on a laptop)
+rm -rf local-snapshots-dir && mkdir local-snapshots-dir
+./mvnw -T 2 -B -DskipTests \
+ -DaltDeploymentRepository=snapshot-repo::default::file:$(pwd)/local-snapshots-dir \
+ clean deploy
+
+# 2) Every optional-plugins.yaml artifact must have a .zip in that tree
+./tools/verify-ci-snapshot-zips.sh
+
+# Full CI flags (longer; needs xvfb for UI tests) — closer to Jenkins:
+# xvfb-run -a --server-args='-screen 0 1280x1024x24' ./mvnw -T 2 -U -B -e -fae -V \
+# -Dmaven.compiler.fork=true -Dsurefire.rerunFailingTestsCount=2 -DSkipTestContainers=true \
+# -DaltDeploymentRepository=snapshot-repo::default::file:$(pwd)/local-snapshots-dir \
+# clean deploy
+```
+
+**After merge to apache/hop main:** wait for Jenkins Deploy green, then check e.g.
+
+`https://repository.apache.org/content/repositories/snapshots/org/apache/hop/hop-tech-parquet/`
+
+for a `.zip` under the SNAPSHOT folder. Private data-hopper deploys do **not** replace this check.
+
+Point marketplace at ASF snapshots when testing SNAPSHOT Hop builds from Apache CI.
+
+### SNAPSHOT to a private Nexus (e.g. data-hopper)
+
+For local or private builders (same idea as Jenkins deploy, different target), use
+a Maven server id + URL such as:
+
+```text
+https://repository.data-hopper.com/repository/apache-hop-plugins/
+```
+
+**Do not** put deploy passwords in the Hop git tree. Use env vars or `~/.m2/settings.xml`.
+
+**Fast path — optional plugin zips only** (recommended for marketplace work):
+
+```bash
+export HOP_DEPLOY_USER=hop_build
+export HOP_DEPLOY_PASSWORD='…' # from your password manager
+export DATA_HOPPER_URL='https://repository.data-hopper.com/repository/apache-hop-plugins'
+./tools/deploy-snapshots-data-hopper.sh marketplace
+```
+
+**Jenkins-like full reactor** (jars + plugin zips; skips `assemblies/` hop-client module):
+
+```xml
+
+
+ apache-hop-plugins
+ hop_build
+ …
+
+```
+
+```bash
+./tools/deploy-snapshots-data-hopper.sh reactor
+# equivalent:
+# ./mvnw clean deploy -DskipTests -P=-assemblies \
+# -DaltDeploymentRepository=apache-hop-plugins::default::https://repository.data-hopper.com/repository/apache-hop-plugins/
+```
+
+Then point Hop at the repo:
+
+```bash
+./hop marketplace repo add --id data-hopper \
+ --url https://repository.data-hopper.com/repository/apache-hop-plugins/ --primary
+./hop marketplace install hop-tech-parquet
+```
+
+Verify zips:
+
+```bash
+export NEXUS_REPO_URL='https://repository.data-hopper.com/repository/apache-hop-plugins/'
+./docker/marketplace-nexus/dummy-staging.sh --skip-publish
+```
+
+Official product defaults remain ASF public + Maven Central; private Nexus is optional.
+
+### Local Nexus / Artifactory
+
+Offline / sandbox work still uses `docker/marketplace-nexus/` and
+`publish-marketplace-plugins.sh` as documented above.
+
+**Dummy staging** (publish every registry zip, HTTP-verify Maven layout, CLI install smoke):
+
+```bash
+./docker/marketplace-nexus/start.sh
+export NEXUS_PASSWORD=hop-nexus-dev
+./docker/marketplace-nexus/dummy-staging.sh
+```
+
+Same script can validate an ASF staging repo with
+`NEXUS_REPO_URL= ./docker/marketplace-nexus/dummy-staging.sh --skip-publish`.
+
+Lean client size check:
+
+```bash
+./tools/check-assembly-size.sh
+# typically hop-client well under the 850 MB limit
+```
diff --git a/plugins/misc/marketplace/pom.xml b/plugins/misc/marketplace/pom.xml
new file mode 100644
index 00000000000..f9aaf689aab
--- /dev/null
+++ b/plugins/misc/marketplace/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.hop
+ hop-plugins-misc
+ 2.19.0-SNAPSHOT
+
+
+ hop-misc-marketplace
+ jar
+ Hop Plugins Miscellaneous Marketplace
+
+
+
+
+ org.apache.hop
+ hop-libs
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.yaml
+ snakeyaml
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.5.0
+
+
+ generate-full-client-env
+
+ exec
+
+ generate-resources
+
+ bash
+
+ ${maven.multiModuleProjectDirectory}/tools/generate-full-client-env.sh
+ ${project.version}
+ ${project.build.directory}/generated/full-client-env.yaml
+
+
+
+
+
+
+
+
diff --git a/plugins/misc/marketplace/src/assembly/assembly.xml b/plugins/misc/marketplace/src/assembly/assembly.xml
new file mode 100644
index 00000000000..9937d0f2546
--- /dev/null
+++ b/plugins/misc/marketplace/src/assembly/assembly.xml
@@ -0,0 +1,57 @@
+
+
+
+
+ hop-misc-marketplace
+
+ zip
+
+ .
+
+
+ ${project.basedir}/src/main/resources/version.xml
+ ${hop.plugin.libdir}
+ true
+
+
+
+ ${project.build.directory}/generated/full-client-env.yaml
+ .
+ full-client-env.yaml
+
+
+ ${project.build.directory}/generated/full-client-env.yaml
+ config/projects/samples/marketplace/
+ full-client-env.yaml
+
+
+
+
+
+ ${project.basedir}/src/main/samples
+ config/projects/samples/marketplace/
+
+
+
+
+ ${maven.multiModuleProjectDirectory}/assemblies/shared/hop-plugin-libs.xml
+
+
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/NexusRepositoryBrowser.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/NexusRepositoryBrowser.java
new file mode 100644
index 00000000000..de49c971e31
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/NexusRepositoryBrowser.java
@@ -0,0 +1,321 @@
+/*
+ * 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.marketplace.catalog;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.json.HopJson;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.marketplace.config.MarketplaceRepository;
+import org.apache.hop.marketplace.resolve.SnapshotVersions;
+
+/**
+ * Best-effort browse of a Sonatype Nexus 3 hosted/proxy Maven repository via REST search for {@code
+ * maven.extension=zip} assets.
+ */
+public final class NexusRepositoryBrowser {
+
+ private NexusRepositoryBrowser() {}
+
+ public static List browse(
+ MarketplaceRepository repository, String textFilter, ILogChannel log) throws HopException {
+ if (repository == null || StringUtils.isBlank(repository.getUrl())) {
+ return List.of();
+ }
+ String repoName = extractRepositoryName(repository.getUrl());
+ String nexusBase = extractNexusBase(repository.getUrl());
+ if (StringUtils.isBlank(repoName) || StringUtils.isBlank(nexusBase)) {
+ throw new HopException(
+ "Cannot derive Nexus host/repository name from URL: " + repository.getUrl());
+ }
+
+ HttpClient client =
+ HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(30))
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
+
+ // One row per groupId:artifactId — keep the latest plugin version (not every unique SNAPSHOT).
+ Map byGa = new LinkedHashMap<>();
+ String continuation = null;
+ int pages = 0;
+ do {
+ pages++;
+ if (pages > 50) {
+ if (log != null) {
+ log.logBasic("Nexus search pagination stopped after 50 pages for " + repoName);
+ }
+ break;
+ }
+ StringBuilder q = new StringBuilder();
+ q.append(nexusBase)
+ .append("/service/rest/v1/search?repository=")
+ .append(enc(repoName))
+ .append("&format=maven2&maven.extension=zip");
+ if (StringUtils.isNotBlank(repository.getGroupIdFilter())) {
+ q.append("&group=").append(enc(repository.getGroupIdFilter()));
+ }
+ if (StringUtils.isNotBlank(textFilter)) {
+ q.append("&name=").append(enc(textFilter.trim()));
+ } else if (StringUtils.isNotBlank(repository.getSearchQuery())) {
+ q.append("&name=").append(enc(repository.getSearchQuery().trim()));
+ }
+ if (StringUtils.isNotBlank(continuation)) {
+ q.append("&continuationToken=").append(enc(continuation));
+ }
+
+ String body = getText(client, q.toString(), repository);
+ Map, ?> json;
+ try {
+ json = HopJson.newMapper().readValue(body, Map.class);
+ } catch (Exception e) {
+ throw new HopException("Unable to parse Nexus search JSON from " + q, e);
+ }
+ Object items = json.get("items");
+ if (items instanceof List> list) {
+ for (Object item : list) {
+ if (!(item instanceof Map, ?> m)) {
+ continue;
+ }
+ String group = str(m.get("group"));
+ String name = str(m.get("name"));
+ String rawVersion = str(m.get("version"));
+ if (StringUtils.isAnyBlank(group, name, rawVersion)) {
+ continue;
+ }
+ // Nexus reports unique SNAPSHOT timestamps (1.0-20260721.105615-1); Maven layout uses
+ // base folder 1.0-SNAPSHOT/. Prefer the version folder from the zip asset path.
+ String zipAssetPath = extractZipAssetPath(m);
+ String version = SnapshotVersions.toBaseVersion(rawVersion, zipAssetPath);
+ if (!repository.isIncludeSnapshots() && SnapshotVersions.isSnapshot(version)) {
+ continue;
+ }
+ String gaKey = group.toLowerCase(Locale.ROOT) + ":" + name.toLowerCase(Locale.ROOT);
+ String lastUpdated = extractLastUpdated(m);
+ OptionalPluginInfo existing = byGa.get(gaKey);
+ if (existing != null && !isPreferable(version, lastUpdated, existing)) {
+ continue;
+ }
+ String path =
+ StringUtils.isNotBlank(zipAssetPath)
+ ? zipAssetPath.replaceFirst("^/+", "")
+ : mavenPath(group, name, version);
+ OptionalPluginInfo info = new OptionalPluginInfo();
+ info.setGroupId(group);
+ info.setArtifactId(name);
+ info.setVersion(version);
+ info.setName(name);
+ info.setCategory("auto-discovered");
+ info.setDescription(path);
+ info.setInstallPath(path);
+ info.setLastUpdated(lastUpdated);
+ info.setSource(repository.getId());
+ byGa.put(gaKey, info);
+ }
+ }
+ Object token = json.get("continuationToken");
+ continuation =
+ token == null || StringUtils.isBlank(String.valueOf(token))
+ ? null
+ : String.valueOf(token);
+ } while (continuation != null);
+
+ List out = new ArrayList<>(byGa.values());
+ if (StringUtils.isNotBlank(textFilter) || StringUtils.isNotBlank(repository.getSearchQuery())) {
+ // client-side refine (server name= is only a partial match)
+ String needle = StringUtils.isNotBlank(textFilter) ? textFilter : repository.getSearchQuery();
+ out = RemotePluginCatalog.filter(out, repository, needle);
+ }
+ return out;
+ }
+
+ /**
+ * Prefer higher plugin version; if versions equal, prefer newer lastUpdated (unique SNAPSHOT
+ * builds collapsed to the same base version).
+ */
+ static boolean isPreferable(
+ String candidateVersion, String candidateUpdated, OptionalPluginInfo existing) {
+ if (existing == null) {
+ return true;
+ }
+ int vc = VersionCompat.compare(candidateVersion, existing.getVersion());
+ if (vc > 0) {
+ return true;
+ }
+ if (vc < 0) {
+ return false;
+ }
+ return isNewer(candidateUpdated, existing.getLastUpdated());
+ }
+
+ static String extractRepositoryName(String repositoryUrl) {
+ if (StringUtils.isBlank(repositoryUrl)) {
+ return null;
+ }
+ String u = repositoryUrl.trim();
+ while (u.endsWith("/")) {
+ u = u.substring(0, u.length() - 1);
+ }
+ int idx = u.indexOf("/repository/");
+ if (idx < 0) {
+ return null;
+ }
+ String rest = u.substring(idx + "/repository/".length());
+ int slash = rest.indexOf('/');
+ return slash < 0 ? rest : rest.substring(0, slash);
+ }
+
+ static String extractNexusBase(String repositoryUrl) {
+ if (StringUtils.isBlank(repositoryUrl)) {
+ return null;
+ }
+ String u = repositoryUrl.trim();
+ int idx = u.indexOf("/repository/");
+ if (idx < 0) {
+ // already a host root?
+ while (u.endsWith("/")) {
+ u = u.substring(0, u.length() - 1);
+ }
+ return u;
+ }
+ return u.substring(0, idx);
+ }
+
+ private static String getText(HttpClient client, String url, MarketplaceRepository repository)
+ throws HopException {
+ try {
+ HttpRequest.Builder b =
+ HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(60)).GET();
+ if (repository.hasCredentials()) {
+ String token =
+ Base64.getEncoder()
+ .encodeToString(
+ (repository.effectiveUsername() + ":" + repository.effectivePassword())
+ .getBytes(StandardCharsets.UTF_8));
+ b.header("Authorization", "Basic " + token);
+ }
+ HttpResponse response = client.send(b.build(), HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() < 200 || response.statusCode() >= 300) {
+ throw new HopException("Nexus search failed HTTP " + response.statusCode() + " for " + url);
+ }
+ return response.body();
+ } catch (HopException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HopException("Nexus search failed for " + url, e);
+ }
+ }
+
+ private static String enc(String s) {
+ return URLEncoder.encode(s, StandardCharsets.UTF_8);
+ }
+
+ private static String str(Object o) {
+ if (o == null) {
+ return null;
+ }
+ String s = String.valueOf(o).trim();
+ return s.isEmpty() ? null : s;
+ }
+
+ static String mavenPath(String group, String artifact, String version) {
+ return group.replace('.', '/') + "/" + artifact + "/" + version + "/";
+ }
+
+ /**
+ * Path of the first {@code .zip} asset (not checksum sidecars), relative to the repository root.
+ */
+ static String extractZipAssetPath(Map, ?> item) {
+ Object assets = item.get("assets");
+ if (!(assets instanceof List> list)) {
+ return null;
+ }
+ for (Object o : list) {
+ if (!(o instanceof Map, ?> asset)) {
+ continue;
+ }
+ String path = str(asset.get("path"));
+ if (path == null) {
+ continue;
+ }
+ String lower = path.toLowerCase(Locale.ROOT);
+ if (lower.endsWith(".zip")
+ && !lower.endsWith(".zip.sha1")
+ && !lower.endsWith(".zip.md5")
+ && !lower.endsWith(".zip.sha256")
+ && !lower.endsWith(".zip.sha512")) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ static String extractLastUpdated(Map, ?> item) {
+ Object assets = item.get("assets");
+ if (assets instanceof List> list && !list.isEmpty()) {
+ // Prefer zip asset lastModified when present
+ for (Object o : list) {
+ if (o instanceof Map, ?> asset) {
+ String path = str(asset.get("path"));
+ if (path != null && path.toLowerCase(Locale.ROOT).endsWith(".zip")) {
+ String lm = str(asset.get("lastModified"));
+ if (lm != null) {
+ return lm;
+ }
+ }
+ }
+ }
+ Object first = list.get(0);
+ if (first instanceof Map, ?> asset) {
+ String lm = str(asset.get("lastModified"));
+ if (lm != null) {
+ return lm;
+ }
+ }
+ }
+ String assetsUpdated = str(item.get("assetsUpdated"));
+ if (assetsUpdated != null) {
+ return assetsUpdated;
+ }
+ return str(item.get("lastModified"));
+ }
+
+ /** Prefer non-blank newer lastUpdated strings (ISO-8601 sorts lexicographically). */
+ static boolean isNewer(String candidate, String existing) {
+ if (StringUtils.isBlank(existing)) {
+ return true;
+ }
+ if (StringUtils.isBlank(candidate)) {
+ return false;
+ }
+ return candidate.compareTo(existing) > 0;
+ }
+}
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginCatalog.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginCatalog.java
new file mode 100644
index 00000000000..e62767e0d45
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginCatalog.java
@@ -0,0 +1,237 @@
+/*
+ * 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.marketplace.catalog;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.yaml.snakeyaml.Yaml;
+
+/**
+ * Catalog of marketplace-optional plugins loaded from {@code optional-plugins.yaml} on the
+ * classpath (source of truth for Wave 1+ optional plugins and full-client-env generation).
+ */
+public final class OptionalPluginCatalog {
+
+ public static final String REGISTRY_RESOURCE =
+ "/org/apache/hop/marketplace/optional-plugins.yaml";
+
+ private static final List PLUGINS = loadRegistry();
+
+ private OptionalPluginCatalog() {}
+
+ public static List listWave1() {
+ return listOptional();
+ }
+
+ /** All marketplace-optional plugins from the registry. */
+ public static List listOptional() {
+ return PLUGINS;
+ }
+
+ /**
+ * Case-insensitive substring filter over artifactId, name, category, description, and
+ * installPath. Blank filter returns all optional plugins.
+ */
+ public static List query(String filter) {
+ if (StringUtils.isBlank(filter)) {
+ return listOptional();
+ }
+ String needle = filter.trim().toLowerCase();
+ List matches = new ArrayList<>();
+ for (OptionalPluginInfo info : PLUGINS) {
+ if (matchesFilter(info, needle)) {
+ matches.add(info);
+ }
+ }
+ return Collections.unmodifiableList(matches);
+ }
+
+ private static boolean matchesFilter(OptionalPluginInfo info, String needleLower) {
+ return containsIgnoreCase(info.getArtifactId(), needleLower)
+ || containsIgnoreCase(info.getName(), needleLower)
+ || containsIgnoreCase(info.getCategory(), needleLower)
+ || containsIgnoreCase(info.getDescription(), needleLower)
+ || containsIgnoreCase(info.getInstallPath(), needleLower);
+ }
+
+ private static boolean containsIgnoreCase(String value, String needleLower) {
+ return value != null && value.toLowerCase().contains(needleLower);
+ }
+
+ public static boolean isInstalledOnDisk(Path hopHome, OptionalPluginInfo info) {
+ if (info.getInstallPath() == null) {
+ return false;
+ }
+ Path path = hopHome.resolve(info.getInstallPath());
+ return Files.isDirectory(path);
+ }
+
+ private static List loadRegistry() {
+ try (InputStream in = OptionalPluginCatalog.class.getResourceAsStream(REGISTRY_RESOURCE)) {
+ if (in == null) {
+ return Collections.unmodifiableList(fallbackCatalog());
+ }
+ Yaml yaml = new Yaml();
+ Object loaded = yaml.load(in);
+ if (!(loaded instanceof Map)) {
+ return Collections.unmodifiableList(fallbackCatalog());
+ }
+ Map root = (Map) loaded;
+ Object pluginsObj = root.get("plugins");
+ if (!(pluginsObj instanceof List)) {
+ return Collections.unmodifiableList(fallbackCatalog());
+ }
+ List result = new ArrayList<>();
+ for (Object entry : (List>) pluginsObj) {
+ if (!(entry instanceof Map)) {
+ continue;
+ }
+ Map m = (Map) entry;
+ String artifactId = stringVal(m.get("artifactId"));
+ if (StringUtils.isBlank(artifactId)) {
+ continue;
+ }
+ result.add(
+ new OptionalPluginInfo(
+ artifactId,
+ stringVal(m.get("name")),
+ stringVal(m.get("category")),
+ stringVal(m.get("description")),
+ stringVal(m.get("installPath"))));
+ }
+ if (result.isEmpty()) {
+ return Collections.unmodifiableList(fallbackCatalog());
+ }
+ return Collections.unmodifiableList(result);
+ } catch (Exception e) {
+ return Collections.unmodifiableList(fallbackCatalog());
+ }
+ }
+
+ private static String stringVal(Object o) {
+ return o == null ? null : String.valueOf(o);
+ }
+
+ /** Hard-coded fallback if the registry resource is missing (tests / broken packaging). */
+ private static List fallbackCatalog() {
+ List wave1 = new ArrayList<>();
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-engines-spark",
+ "Native Spark engine",
+ "Engines",
+ "Run pipelines on Apache Spark (local or cluster).",
+ "plugins/engines/spark"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-engines-beam",
+ "Apache Beam engine",
+ "Engines",
+ "Beam pipeline engine and transforms (includes Beam SDKs under lib-beam).",
+ "plugins/engines/beam"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-transform-script",
+ "Script transform",
+ "Transforms",
+ "Scripting transform with embedded language runtimes.",
+ "plugins/transforms/script"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-tech-cassandra",
+ "Cassandra",
+ "Technology",
+ "Apache Cassandra input/output and related tech.",
+ "plugins/tech/cassandra"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-transform-tika",
+ "Apache Tika",
+ "Transforms",
+ "Extract text and metadata from documents via Apache Tika.",
+ "plugins/transforms/tika"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-transform-drools",
+ "Drools",
+ "Transforms",
+ "Business rules with Drools.",
+ "plugins/transforms/drools"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-tech-parquet",
+ "Parquet",
+ "Technology",
+ "Parquet file format support.",
+ "plugins/tech/parquet"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-transform-stanfordnlp",
+ "Stanford NLP",
+ "Transforms",
+ "Natural language processing with Stanford NLP.",
+ "plugins/transforms/stanfordnlp"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-tech-arrow",
+ "Apache Arrow",
+ "Technology",
+ "Apache Arrow integration.",
+ "plugins/tech/arrow"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-tech-dropbox",
+ "Dropbox",
+ "Technology",
+ "Dropbox VFS and related support.",
+ "plugins/tech/dropbox"));
+ wave1.add(
+ new OptionalPluginInfo(
+ "hop-transform-edi2xml",
+ "EDI to XML",
+ "Transforms",
+ "Convert EDI messages to XML.",
+ "plugins/transforms/edi2xml"));
+ return wave1;
+ }
+
+ /** Artifact ids only (for tests / tooling). */
+ public static List artifactIds() {
+ List ids = new ArrayList<>();
+ for (OptionalPluginInfo info : PLUGINS) {
+ ids.add(info.getArtifactId());
+ }
+ return ids;
+ }
+
+ /** Expose registry as simple maps for tests. */
+ public static Map installPathsByArtifactId() {
+ Map map = new LinkedHashMap<>();
+ for (OptionalPluginInfo info : PLUGINS) {
+ map.put(info.getArtifactId(), info.getInstallPath());
+ }
+ return map;
+ }
+}
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginInfo.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginInfo.java
new file mode 100644
index 00000000000..db5d1acac19
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/OptionalPluginInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.marketplace.catalog;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class OptionalPluginInfo {
+ private String groupId;
+ private String artifactId;
+ private String version;
+ private String name;
+ private String category;
+ private String description;
+ private String installPath;
+
+ /** Where this entry came from (e.g. {@code apache}, repository id, catalog URL). */
+ private String source;
+
+ /** Last modified time from remote repository (ISO-8601 or raw Nexus value), if known. */
+ private String lastUpdated;
+
+ /**
+ * Minimum Hop version this plugin supports (e.g. {@code 2.18.1}). When set, discovery hides the
+ * plugin if the running Hop version is older.
+ */
+ private String minHopVersion;
+
+ /**
+ * Optional maximum Hop version this plugin supports. When set, discovery hides the plugin if the
+ * running Hop version is newer.
+ */
+ private String maxHopVersion;
+
+ public OptionalPluginInfo() {
+ // Jackson / default
+ }
+
+ public OptionalPluginInfo(
+ String artifactId, String name, String category, String description, String installPath) {
+ this.artifactId = artifactId;
+ this.name = name;
+ this.category = category;
+ this.description = description;
+ this.installPath = installPath;
+ this.source = "apache";
+ }
+}
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/PluginDiscovery.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/PluginDiscovery.java
new file mode 100644
index 00000000000..b6fbdf81103
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/PluginDiscovery.java
@@ -0,0 +1,402 @@
+/*
+ * 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.marketplace.catalog;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.HopVersionProvider;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.marketplace.config.MarketplaceConfig;
+import org.apache.hop.marketplace.config.MarketplaceRepository;
+import org.apache.hop.marketplace.resolve.MavenCoordinates;
+
+/**
+ * Discovers marketplace plugins for query / GUI / install coordinate resolution.
+ *
+ *
Always includes the bundled Apache optional catalog, plus a live listing of
+ * every enabled repository with {@code browse=true} (Nexus zip search). Optional plugin metadata
+ * embedded in the repository definition enriches live results; it is not a cache.
+ */
+public final class PluginDiscovery {
+
+ private PluginDiscovery() {}
+
+ /**
+ * Resolved install target: Maven coordinates plus an optional preferred repository id (from
+ * discovery {@code source}) tried first in the install fallback chain.
+ */
+ public record InstallTarget(MavenCoordinates coordinates, String preferredRepoId) {}
+
+ /**
+ * Full marketplace discovery: bundled Apache optional catalog plus every enabled repository with
+ * {@code browse=true} (live Nexus zip list or optional catalog URL).
+ *
+ * @param filter free-text filter (may be blank)
+ * @param repoId if non-blank, only that browse repository among remotes (Apache catalog still
+ * included)
+ */
+ public static List query(
+ String filter, String repoId, MarketplaceConfig config, ILogChannel log) {
+ String hopVersion = resolveHopVersion(config);
+ Map byKey = new LinkedHashMap<>();
+
+ for (OptionalPluginInfo info : OptionalPluginCatalog.query(filter)) {
+ if (info == null) {
+ continue;
+ }
+ // Apache optional plugins track the running Hop line — not absolute repo .
+ if (StringUtils.isBlank(info.getVersion()) && StringUtils.isNotBlank(hopVersion)) {
+ info.setVersion(hopVersion);
+ }
+ if (StringUtils.isBlank(info.getSource())) {
+ info.setSource("apache");
+ }
+ byKey.putIfAbsent(key(info), info);
+ }
+
+ if (config != null && config.getRepositories() != null) {
+ for (MarketplaceRepository repo : config.getRepositories()) {
+ if (repo == null || !repo.isEnabled() || !repo.isBrowse()) {
+ continue;
+ }
+ if (StringUtils.isNotBlank(repoId) && !repoId.equals(repo.getId())) {
+ continue;
+ }
+ try {
+ for (OptionalPluginInfo info : discoverRepoLive(repo, filter, log)) {
+ if (info == null) {
+ continue;
+ }
+ if (!VersionCompat.isCompatibleWithHop(info, hopVersion)) {
+ continue;
+ }
+ byKey.putIfAbsent(key(info), info);
+ }
+ } catch (Exception e) {
+ if (log != null) {
+ log.logError(
+ "Marketplace discovery failed for repository '"
+ + repo.getId()
+ + "': "
+ + e.getMessage(),
+ e);
+ }
+ }
+ }
+ }
+
+ return new ArrayList<>(byKey.values());
+ }
+
+ /**
+ * Resolve an install coordinate the same way users search with {@code marketplace query}.
+ *
+ *
+ *
{@code groupId:artifactId:version} — used as-is (no discovery)
+ *
{@code artifactId} or short name (e.g. {@code datavault}) — discovery for unique match;
+ * uses discovered artifact id, group, version, and preferred source repo
+ *
{@code artifactId:version} — discovery for artifact/group/source; version from the user
+ *
No discovery hit — falls back to literal {@link MavenCoordinates#parse} with defaults
+ *
+ */
+ public static InstallTarget resolveInstall(
+ String spec,
+ String defaultGroupId,
+ String defaultVersion,
+ MarketplaceConfig config,
+ ILogChannel log)
+ throws HopException {
+ if (StringUtils.isBlank(spec)) {
+ throw new HopException("Plugin coordinate is empty");
+ }
+ String trimmed = spec.trim();
+ String[] parts = trimmed.split(":");
+ if (parts.length > 3) {
+ throw new HopException(
+ "Invalid coordinate '"
+ + spec
+ + "'. Use artifactId, artifactId:version, or groupId:artifactId:version");
+ }
+ if (parts.length == 3) {
+ return new InstallTarget(new MavenCoordinates(parts[0], parts[1], parts[2]), null);
+ }
+
+ String token = parts[0];
+ String userVersion = parts.length == 2 ? parts[1] : null;
+
+ List matches = query(token, null, config, log);
+ OptionalPluginInfo chosen = pickInstallMatch(token, matches);
+ if (chosen == null) {
+ return new InstallTarget(
+ MavenCoordinates.parse(trimmed, defaultGroupId, defaultVersion), null);
+ }
+
+ String groupId =
+ StringUtils.isNotBlank(chosen.getGroupId()) ? chosen.getGroupId() : defaultGroupId;
+ String artifactId = chosen.getArtifactId();
+ String version;
+ if (StringUtils.isNotBlank(userVersion)) {
+ version = userVersion;
+ } else if (StringUtils.isNotBlank(chosen.getVersion())) {
+ version = chosen.getVersion();
+ } else {
+ version = defaultVersion;
+ }
+ if (StringUtils.isBlank(version)) {
+ throw new HopException(
+ "No version for '"
+ + artifactId
+ + "'. Use artifactId:version or groupId:artifactId:version");
+ }
+
+ String preferredRepo = null;
+ if (StringUtils.isNotBlank(chosen.getSource())
+ && !"apache".equalsIgnoreCase(chosen.getSource())
+ && config != null
+ && config.findRepository(chosen.getSource()) != null) {
+ preferredRepo = chosen.getSource();
+ }
+
+ return new InstallTarget(new MavenCoordinates(groupId, artifactId, version), preferredRepo);
+ }
+
+ /**
+ * Choose a single install target from discovery matches for {@code token} (artifact id or short
+ * name). Prefer exact artifact id, then unique artifact-id substring match, then unique overall
+ * match. Throws when multiple candidates remain.
+ */
+ static OptionalPluginInfo pickInstallMatch(String token, List matches)
+ throws HopException {
+ if (matches == null || matches.isEmpty() || StringUtils.isBlank(token)) {
+ return null;
+ }
+ String needle = token.trim().toLowerCase(Locale.ROOT);
+
+ List exactArtifact = new ArrayList<>();
+ List artifactContains = new ArrayList<>();
+ for (OptionalPluginInfo info : matches) {
+ if (info == null || StringUtils.isBlank(info.getArtifactId())) {
+ continue;
+ }
+ String aid = info.getArtifactId().toLowerCase(Locale.ROOT);
+ if (aid.equals(needle)) {
+ exactArtifact.add(info);
+ } else if (aid.contains(needle)) {
+ artifactContains.add(info);
+ }
+ }
+
+ if (exactArtifact.size() == 1) {
+ return exactArtifact.get(0);
+ }
+ if (exactArtifact.size() > 1) {
+ // Same artifact from multiple sources/versions — prefer first (query order)
+ return exactArtifact.get(0);
+ }
+ if (artifactContains.size() == 1) {
+ return artifactContains.get(0);
+ }
+ if (artifactContains.size() > 1) {
+ throw ambiguous(token, artifactContains);
+ }
+
+ // Description/name-only hits: allow only when unique
+ List withArtifact =
+ matches.stream()
+ .filter(i -> i != null && StringUtils.isNotBlank(i.getArtifactId()))
+ .collect(Collectors.toList());
+ if (withArtifact.size() == 1) {
+ return withArtifact.get(0);
+ }
+ if (withArtifact.size() > 1) {
+ throw ambiguous(token, withArtifact);
+ }
+ return null;
+ }
+
+ private static HopException ambiguous(String token, List candidates) {
+ String list =
+ candidates.stream()
+ .map(
+ i ->
+ " - "
+ + nullToEmpty(i.getGroupId())
+ + ":"
+ + i.getArtifactId()
+ + ":"
+ + (StringUtils.isNotBlank(i.getVersion()) ? i.getVersion() : "?")
+ + (StringUtils.isNotBlank(i.getSource()) ? " [" + i.getSource() + "]" : ""))
+ .collect(Collectors.joining("\n"));
+ return new HopException(
+ "Ambiguous install target '"
+ + token
+ + "'. Matches:\n"
+ + list
+ + "\nUse a full artifactId or groupId:artifactId:version.");
+ }
+
+ /**
+ * Live list for one repository: Nexus zip search (or catalog URL), enriched with any plugin
+ * metadata from the repository definition.
+ */
+ public static List discoverRepoLive(
+ MarketplaceRepository repo, String filter, ILogChannel log) throws Exception {
+ if (repo == null) {
+ return List.of();
+ }
+
+ List live;
+ if (StringUtils.isNotBlank(repo.getCatalogUrl())) {
+ live = RemotePluginCatalog.load(repo);
+ live = RemotePluginCatalog.filter(live, repo, filter);
+ } else {
+ live = NexusRepositoryBrowser.browse(repo, filter, log);
+ }
+
+ // If live listing failed empty but definition has explicit plugins, use those (not a cache —
+ // author-supplied list in the YAML).
+ if (live.isEmpty() && repo.getPlugins() != null && !repo.getPlugins().isEmpty()) {
+ return RemotePluginCatalog.filter(repo.getPlugins(), repo, filter);
+ }
+
+ return enrichWithDefinitionMetadata(live, repo);
+ }
+
+ /**
+ * Apply name/description/category/hop-range and optional version pin from definition plugins when
+ * artifact ids match (groupId optional on either side).
+ */
+ static List enrichWithDefinitionMetadata(
+ List live, MarketplaceRepository repo) {
+ if (live == null
+ || live.isEmpty()
+ || repo.getPlugins() == null
+ || repo.getPlugins().isEmpty()) {
+ return live == null ? List.of() : live;
+ }
+ Map byGa = new LinkedHashMap<>();
+ Map byArtifact = new LinkedHashMap<>();
+ for (OptionalPluginInfo p : repo.getPlugins()) {
+ if (p == null || StringUtils.isBlank(p.getArtifactId())) {
+ continue;
+ }
+ byGa.putIfAbsent(artifactKey(p), p);
+ byArtifact.putIfAbsent(p.getArtifactId().toLowerCase(Locale.ROOT), p);
+ }
+ for (OptionalPluginInfo info : live) {
+ if (info == null || StringUtils.isBlank(info.getArtifactId())) {
+ continue;
+ }
+ OptionalPluginInfo m = byGa.get(artifactKey(info));
+ if (m == null) {
+ m = byArtifact.get(info.getArtifactId().toLowerCase(Locale.ROOT));
+ }
+ if (m == null) {
+ continue;
+ }
+ if (StringUtils.isNotBlank(m.getName())
+ && (StringUtils.isBlank(info.getName()) || info.getName().equals(info.getArtifactId()))) {
+ info.setName(m.getName());
+ }
+ if (StringUtils.isNotBlank(m.getDescription())
+ && (StringUtils.isBlank(info.getDescription())
+ || "auto-discovered".equals(info.getCategory())
+ || looksLikeMavenPath(info.getDescription()))) {
+ if (!looksLikeMavenPath(m.getDescription())) {
+ info.setDescription(m.getDescription());
+ }
+ }
+ // Prefer human category from YAML over Nexus auto-discovered
+ if (StringUtils.isNotBlank(m.getCategory())
+ && (StringUtils.isBlank(info.getCategory())
+ || "auto-discovered".equalsIgnoreCase(info.getCategory())
+ || !"auto-discovered".equalsIgnoreCase(m.getCategory()))) {
+ if (!"auto-discovered".equalsIgnoreCase(m.getCategory())
+ || StringUtils.isBlank(info.getCategory())) {
+ info.setCategory(m.getCategory());
+ }
+ }
+ if (StringUtils.isNotBlank(m.getMinHopVersion())) {
+ info.setMinHopVersion(m.getMinHopVersion());
+ }
+ if (StringUtils.isNotBlank(m.getMaxHopVersion())) {
+ info.setMaxHopVersion(m.getMaxHopVersion());
+ }
+ // Optional pin of plugin artifact version from the shareable definition
+ if (StringUtils.isNotBlank(m.getVersion())) {
+ info.setVersion(m.getVersion());
+ }
+ if (StringUtils.isBlank(info.getGroupId()) && StringUtils.isNotBlank(m.getGroupId())) {
+ info.setGroupId(m.getGroupId());
+ }
+ }
+ return live;
+ }
+
+ private static boolean looksLikeMavenPath(String s) {
+ return s != null && s.contains("/") && s.contains(".");
+ }
+
+ static String artifactKey(OptionalPluginInfo info) {
+ String g =
+ StringUtils.isNotBlank(info.getGroupId())
+ ? info.getGroupId().toLowerCase(Locale.ROOT)
+ : "org.apache.hop";
+ return g + ":" + info.getArtifactId().toLowerCase(Locale.ROOT);
+ }
+
+ private static String key(OptionalPluginInfo info) {
+ return nullToEmpty(info.getGroupId())
+ + ":"
+ + nullToEmpty(info.getArtifactId())
+ + ":"
+ + nullToEmpty(info.getVersion())
+ + ":"
+ + nullToEmpty(info.getSource());
+ }
+
+ private static String nullToEmpty(String s) {
+ return s == null ? "" : s;
+ }
+
+ /**
+ * Running Hop / marketplace default version — used for Apache optional plugin coordinates and
+ * minHopVersion checks. Mirrors {@code MarketplaceCommand.resolveDefaultVersion} without a
+ * circular dependency on the CLI class.
+ */
+ static String resolveHopVersion(MarketplaceConfig config) {
+ if (config != null && StringUtils.isNotBlank(config.getDefaultVersion())) {
+ return config.getDefaultVersion();
+ }
+ try {
+ String[] versions = new HopVersionProvider().getVersion();
+ if (versions != null && versions.length > 0 && StringUtils.isNotBlank(versions[0])) {
+ return versions[0];
+ }
+ } catch (Exception ignored) {
+ // fall through
+ }
+ return System.getProperty("hop.version", "2.19.0-SNAPSHOT");
+ }
+}
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/RemotePluginCatalog.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/RemotePluginCatalog.java
new file mode 100644
index 00000000000..61dc658859c
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/RemotePluginCatalog.java
@@ -0,0 +1,214 @@
+/*
+ * 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.marketplace.catalog;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.json.HopJson;
+import org.apache.hop.marketplace.config.MarketplaceRepository;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
+
+/** Loads a remote or local hop-marketplace-catalog YAML/JSON. */
+public final class RemotePluginCatalog {
+
+ private RemotePluginCatalog() {}
+
+ public static List load(MarketplaceRepository repository)
+ throws HopException {
+ if (repository == null || StringUtils.isBlank(repository.getCatalogUrl())) {
+ return List.of();
+ }
+ String url = repository.getCatalogUrl().trim();
+ List plugins;
+ if (url.startsWith("http://") || url.startsWith("https://")) {
+ plugins = loadHttp(url);
+ } else {
+ plugins = loadFile(Path.of(url));
+ }
+ String source =
+ StringUtils.isNotBlank(repository.getId()) ? repository.getId() : repository.displayName();
+ for (OptionalPluginInfo p : plugins) {
+ if (p != null && StringUtils.isBlank(p.getSource())) {
+ p.setSource(source);
+ }
+ if (p != null && StringUtils.isBlank(p.getGroupId())) {
+ p.setGroupId("org.apache.hop");
+ }
+ }
+ return filter(plugins, repository, null);
+ }
+
+ public static List loadFile(Path file) throws HopException {
+ if (file == null || !Files.isRegularFile(file)) {
+ throw new HopException("Catalog file not found: " + file);
+ }
+ try (InputStream in = Files.newInputStream(file)) {
+ return parse(in, file.toString());
+ } catch (Exception e) {
+ throw new HopException("Unable to read catalog: " + file, e);
+ }
+ }
+
+ public static List loadHttp(String url) throws HopException {
+ try {
+ HttpClient client =
+ HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(30))
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
+ HttpRequest request =
+ HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(60)).GET().build();
+ HttpResponse response =
+ client.send(request, HttpResponse.BodyHandlers.ofInputStream());
+ if (response.statusCode() < 200 || response.statusCode() >= 300) {
+ throw new HopException(
+ "Unable to download catalog from " + url + " (HTTP " + response.statusCode() + ")");
+ }
+ try (InputStream in = response.body()) {
+ return parse(in, url);
+ }
+ } catch (HopException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HopException("Unable to download catalog from " + url, e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static List parse(InputStream in, String source) throws HopException {
+ try {
+ String lower = source == null ? "" : source.toLowerCase(Locale.ROOT);
+ Map root;
+ if (lower.endsWith(".json")) {
+ root = HopJson.newMapper().readValue(in, Map.class);
+ } else {
+ Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
+ Object loaded = yaml.load(in);
+ if (!(loaded instanceof Map)) {
+ throw new HopException("Catalog root must be a mapping: " + source);
+ }
+ root = (Map) loaded;
+ }
+ Object pluginsNode = root.get("plugins");
+ if (!(pluginsNode instanceof List> list)) {
+ return List.of();
+ }
+ List out = new ArrayList<>();
+ for (Object item : list) {
+ if (!(item instanceof Map, ?> m)) {
+ continue;
+ }
+ OptionalPluginInfo info = new OptionalPluginInfo();
+ info.setGroupId(str(m.get("groupId")));
+ info.setArtifactId(str(m.get("artifactId")));
+ info.setVersion(str(m.get("version")));
+ info.setName(str(m.get("name")));
+ info.setCategory(str(m.get("category")));
+ info.setDescription(str(m.get("description")));
+ info.setInstallPath(str(m.get("installPath")));
+ info.setMinHopVersion(str(m.get("minHopVersion")));
+ info.setMaxHopVersion(str(m.get("maxHopVersion")));
+ info.setSource(str(m.get("source")));
+ if (StringUtils.isNotBlank(info.getArtifactId())) {
+ out.add(info);
+ }
+ }
+ return out;
+ } catch (HopException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HopException("Unable to parse catalog: " + source, e);
+ }
+ }
+
+ /** Apply repository filters and optional free-text filter. */
+ public static List filter(
+ List plugins, MarketplaceRepository repository, String textFilter) {
+ if (plugins == null || plugins.isEmpty()) {
+ return List.of();
+ }
+ String groupFilter =
+ repository != null ? StringUtils.trimToNull(repository.getGroupIdFilter()) : null;
+ boolean includeSnapshots = repository == null || repository.isIncludeSnapshots();
+ String repoQuery =
+ repository != null ? StringUtils.trimToNull(repository.getSearchQuery()) : null;
+ String text = StringUtils.trimToNull(textFilter);
+ List out = new ArrayList<>();
+ for (OptionalPluginInfo p : plugins) {
+ if (p == null || StringUtils.isBlank(p.getArtifactId())) {
+ continue;
+ }
+ if (groupFilter != null
+ && StringUtils.isNotBlank(p.getGroupId())
+ && !groupFilter.equals(p.getGroupId())) {
+ continue;
+ }
+ if (!includeSnapshots
+ && p.getVersion() != null
+ && org.apache.hop.marketplace.resolve.SnapshotVersions.isSnapshot(p.getVersion())) {
+ continue;
+ }
+ if (repoQuery != null && !matchesText(p, repoQuery)) {
+ continue;
+ }
+ if (text != null && !matchesText(p, text)) {
+ continue;
+ }
+ out.add(p);
+ }
+ return out;
+ }
+
+ static boolean matchesText(OptionalPluginInfo p, String needle) {
+ String n = needle.toLowerCase(Locale.ROOT);
+ return contains(p.getArtifactId(), n)
+ || contains(p.getName(), n)
+ || contains(p.getCategory(), n)
+ || contains(p.getDescription(), n)
+ || contains(p.getGroupId(), n)
+ || contains(p.getVersion(), n)
+ || contains(p.getInstallPath(), n)
+ || contains(p.getSource(), n);
+ }
+
+ private static boolean contains(String value, String needleLower) {
+ return value != null && value.toLowerCase(Locale.ROOT).contains(needleLower);
+ }
+
+ private static String str(Object o) {
+ if (o == null) {
+ return null;
+ }
+ String s = String.valueOf(o).trim();
+ return s.isEmpty() ? null : s;
+ }
+}
diff --git a/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/VersionCompat.java b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/VersionCompat.java
new file mode 100644
index 00000000000..681fb3ec266
--- /dev/null
+++ b/plugins/misc/marketplace/src/main/java/org/apache/hop/marketplace/catalog/VersionCompat.java
@@ -0,0 +1,160 @@
+/*
+ * 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.marketplace.catalog;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Hop / plugin version comparison and compatibility checks for marketplace discovery.
+ *
+ *
Lightweight Maven-like ordering: numeric segments, then qualifiers; {@code -SNAPSHOT} is
+ * treated as a pre-release of the same base version.
+ */
+public final class VersionCompat {
+
+ private VersionCompat() {}
+
+ /**
+ * Whether {@code info} may be offered on the given running Hop version. Blank min/max means no
+ * bound on that side. Missing hop version fails closed only when a bound is present.
+ */
+ public static boolean isCompatibleWithHop(OptionalPluginInfo info, String hopVersion) {
+ if (info == null) {
+ return false;
+ }
+ String min = info.getMinHopVersion();
+ String max = info.getMaxHopVersion();
+ if (StringUtils.isBlank(min) && StringUtils.isBlank(max)) {
+ return true;
+ }
+ if (StringUtils.isBlank(hopVersion)) {
+ return false;
+ }
+ if (StringUtils.isNotBlank(min) && compare(hopVersion, min) < 0) {
+ return false;
+ }
+ if (StringUtils.isNotBlank(max) && compare(hopVersion, max) > 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return negative if a < b, zero if equal, positive if a > b
+ */
+ public static int compare(String a, String b) {
+ List