This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new fc9e0ff63878 CAMEL-23169: camel-jbang - Export to standalone should
package fat-ja… (#21930)
fc9e0ff63878 is described below
commit fc9e0ff63878b9c1eee2e7c011649d03c525f7b1
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Mar 11 12:12:44 2026 +0100
CAMEL-23169: camel-jbang - Export to standalone should package fat-ja…
(#21930)
* CAMEL-23169: camel-jbang - Export to standalone should package fat-jar
using camel-repacker-plugin which we use in camel-launcher and spring boot as
well.
---
.../apache/camel/language/jq/JqBuiltInFnTest.java | 49 +++++++
.../fatjar/FatJarPackageScanClassResolver.java | 136 +++++++++++++++++++
.../fatjar/FatJarPackageScanResourceResolver.java | 150 +++++++++++++++++++++
.../java/org/apache/camel/main/fatjar/Main.java | 66 +++++++++
.../src/main/resources/templates/main-pom.tmpl | 27 ++--
.../src/main/resources/templates/main.tmpl | 2 +-
6 files changed, 410 insertions(+), 20 deletions(-)
diff --git
a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBuiltInFnTest.java
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBuiltInFnTest.java
new file mode 100644
index 000000000000..6e30288a84b3
--- /dev/null
+++
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqBuiltInFnTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.camel.language.jq;
+
+import java.net.InetAddress;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+public class JqBuiltInFnTest extends JqTestSupport {
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .transform().jq(".foo = hostname")
+ .to("mock:result");
+ }
+ };
+ }
+
+ @Test
+ public void testExpression() throws Exception {
+ InetAddress addr = InetAddress.getLocalHost();
+
+ getMockEndpoint("mock:result")
+ .expectedBodiesReceived(node("foo", addr.getHostName()));
+
+ template.sendBodyAndHeader("direct:start", node("foo", "bar"),
"MyHeader", "MyValue");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanClassResolver.java
b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanClassResolver.java
new file mode 100644
index 000000000000..876e7c60707f
--- /dev/null
+++
b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanClassResolver.java
@@ -0,0 +1,136 @@
+/*
+ * 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.camel.main.fatjar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.camel.support.scan.DefaultPackageScanClassResolver;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of the {@code
org.apache.camel.spi.PackageScanClassResolver} that is able to scan spring-boot
fat
+ * jars to find classes contained also in nested jars.
+ */
+public class FatJarPackageScanClassResolver extends
DefaultPackageScanClassResolver {
+ private static final Logger LOG =
LoggerFactory.getLogger(FatJarPackageScanClassResolver.class);
+
+ private static final String SPRING_BOOT_CLASSIC_LIB_ROOT = "lib/";
+ private static final String SPRING_BOOT_BOOT_INF_LIB_ROOT =
"BOOT-INF/lib/";
+ private static final String SPRING_BOOT_BOOT_INF_CLASSES_ROOT =
"BOOT-INF/classes/";
+ private static final String SPRING_BOOT_WEB_INF_LIB_ROOT = "WEB-INF/lib/";
+ private static final String SPRING_BOOT_WEB_INF_CLASSES_ROOT =
"WEB-INF/classes/";
+
+ @Override
+ protected List<String> doLoadJarClassEntries(InputStream stream, String
urlPath) {
+ return doLoadJarClassEntries(stream, urlPath, true, true);
+ }
+
+ @Override
+ protected String parseUrlPath(URL url) {
+ String urlPath = url.getFile();
+
+ urlPath = URLDecoder.decode(urlPath, StandardCharsets.UTF_8);
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Decoded urlPath: {} with protocol: {}", urlPath,
url.getProtocol());
+ }
+
+ String nested = "nested:";
+ if (urlPath.startsWith(nested)) {
+ try {
+ urlPath = (new URI(url.getFile())).getPath();
+ return StringHelper.before(urlPath, "!", urlPath);
+ } catch (URISyntaxException e) {
+ // ignore
+ }
+ if (urlPath.startsWith(nested)) {
+ urlPath = urlPath.substring(nested.length());
+ return StringHelper.before(urlPath, "!", urlPath);
+ }
+ }
+
+ return super.parseUrlPath(url);
+ }
+
+ protected List<String> doLoadJarClassEntries(
+ InputStream stream, String urlPath, boolean inspectNestedJars,
+ boolean closeStream) {
+ List<String> entries = new ArrayList<>();
+
+ JarInputStream jarStream = null;
+ try {
+ jarStream = new JarInputStream(stream);
+
+ JarEntry entry;
+ while ((entry = jarStream.getNextJarEntry()) != null) {
+ String name = entry.getName();
+
+ name = name.trim();
+ if (!entry.isDirectory() && name.endsWith(".class")) {
+ entries.add(cleanupSpringBootClassName(name));
+ } else if (inspectNestedJars && !entry.isDirectory() &&
isSpringBootNestedJar(name)) {
+ String nestedUrl = urlPath + "!/" + name;
+ LOG.trace("Inspecting nested jar: {}", nestedUrl);
+
+ List<String> nestedEntries =
doLoadJarClassEntries(jarStream, nestedUrl, false, false);
+ entries.addAll(nestedEntries);
+ }
+ }
+ } catch (IOException ioe) {
+ LOG.warn("Cannot search jar file '" + urlPath + " due to an
IOException: " + ioe.getMessage()
+ + ". This exception is ignored.",
+ ioe);
+ } finally {
+ if (closeStream) {
+ // stream is left open when scanning nested jars, otherwise
the fat jar stream gets closed
+ IOHelper.close(jarStream, urlPath, LOG);
+ }
+ }
+
+ return entries;
+ }
+
+ private boolean isSpringBootNestedJar(String name) {
+ // Supporting both versions of the packaging model
+ return name.endsWith(".jar") &&
(name.startsWith(SPRING_BOOT_CLASSIC_LIB_ROOT)
+ || name.startsWith(SPRING_BOOT_BOOT_INF_LIB_ROOT) ||
name.startsWith(SPRING_BOOT_WEB_INF_LIB_ROOT));
+ }
+
+ private String cleanupSpringBootClassName(String name) {
+ // Classes inside BOOT-INF/classes will be loaded by the new
classloader as if they were in the root
+ if (name.startsWith(SPRING_BOOT_BOOT_INF_CLASSES_ROOT)) {
+ name = name.substring(SPRING_BOOT_BOOT_INF_CLASSES_ROOT.length());
+ }
+ if (name.startsWith(SPRING_BOOT_WEB_INF_CLASSES_ROOT)) {
+ name = name.substring(SPRING_BOOT_WEB_INF_CLASSES_ROOT.length());
+ }
+ return name;
+ }
+
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanResourceResolver.java
b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanResourceResolver.java
new file mode 100644
index 000000000000..7592bb1b94d2
--- /dev/null
+++
b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/FatJarPackageScanResourceResolver.java
@@ -0,0 +1,150 @@
+/*
+ * 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.camel.main.fatjar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.camel.support.scan.DefaultPackageScanResourceResolver;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An implementation of the {@code
org.apache.camel.spi.PackageScanResourceResolver} that is able to scan
spring-boot
+ * fat jars to find resources contained also in nested jars.
+ */
+public class FatJarPackageScanResourceResolver extends
DefaultPackageScanResourceResolver {
+ private static final Logger LOG =
LoggerFactory.getLogger(FatJarPackageScanResourceResolver.class);
+
+ private static final String SPRING_BOOT_CLASSIC_LIB_ROOT = "lib/";
+ private static final String SPRING_BOOT_BOOT_INF_LIB_ROOT =
"BOOT-INF/lib/";
+ private static final String SPRING_BOOT_BOOT_INF_CLASSES_ROOT =
"BOOT-INF/classes/";
+ private static final String SPRING_BOOT_WEB_INF_LIB_ROOT = "WEB-INF/lib/";
+ private static final String SPRING_BOOT_WEB_INF_CLASSES_ROOT =
"WEB-INF/classes/";
+
+ @Override
+ protected List<String> doLoadImplementationsInJar(
+ String packageName, InputStream stream, String urlPath,
Predicate<String> filter) {
+ return doLoadImplementationsInJar(packageName, stream, urlPath, true,
true, filter);
+ }
+
+ protected List<String> doLoadImplementationsInJar(
+ String packageName, InputStream stream, String urlPath,
+ boolean inspectNestedJars, boolean closeStream, Predicate<String>
filter) {
+ List<String> entries = new ArrayList<>();
+
+ JarInputStream jarStream = null;
+ try {
+ jarStream = new JarInputStream(stream);
+
+ JarEntry entry;
+ while ((entry = jarStream.getNextJarEntry()) != null) {
+ String name = entry.getName().trim();
+ if (inspectNestedJars && !entry.isDirectory() &&
isSpringBootNestedJar(name)) {
+ String nestedUrl = urlPath + "!/" + name;
+ LOG.trace("Inspecting nested jar: {}", nestedUrl);
+ List<String> nestedEntries =
doLoadImplementationsInJar(packageName, jarStream, nestedUrl, false,
+ false, filter);
+ entries.addAll(nestedEntries);
+ } else if (!entry.isDirectory()) {
+ boolean accept;
+ if (filter != null) {
+ // use filter to accept or not
+ accept = filter.test(name);
+ } else {
+ // skip class files by default
+ accept = !name.endsWith(".class");
+ }
+ if (accept) {
+ name = cleanupSpringBootClassName(name);
+ // name is FQN so it must start with package name
+ if (name.startsWith(packageName)) {
+ entries.add(name);
+ }
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ LOG.warn("Cannot search jar file '" + urlPath + " due to an
IOException: " + ioe.getMessage()
+ + ". This exception is ignored.",
+ ioe);
+ } finally {
+ if (closeStream) {
+ // stream is left open when scanning nested jars, otherwise
the fat jar stream gets closed
+ IOHelper.close(jarStream, urlPath, LOG);
+ }
+ }
+
+ return entries;
+ }
+
+ @Override
+ protected String parseUrlPath(URL url) {
+ String urlPath = url.getFile();
+
+ urlPath = URLDecoder.decode(urlPath, StandardCharsets.UTF_8);
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Decoded urlPath: {} with protocol: {}", urlPath,
url.getProtocol());
+ }
+
+ String nested = "nested:";
+ if (urlPath.startsWith(nested)) {
+ try {
+ urlPath = (new URI(url.getFile())).getPath();
+ return StringHelper.before(urlPath, "!", urlPath);
+ } catch (URISyntaxException e) {
+ // ignore
+ }
+ if (urlPath.startsWith(nested)) {
+ urlPath = urlPath.substring(nested.length());
+ return StringHelper.before(urlPath, "!", urlPath);
+ }
+ }
+
+ return super.parseUrlPath(url);
+ }
+
+ private boolean isSpringBootNestedJar(String name) {
+ // Supporting both versions of the packaging model
+ return name.endsWith(".jar") &&
(name.startsWith(SPRING_BOOT_CLASSIC_LIB_ROOT)
+ || name.startsWith(SPRING_BOOT_BOOT_INF_LIB_ROOT) ||
name.startsWith(SPRING_BOOT_WEB_INF_LIB_ROOT));
+ }
+
+ private String cleanupSpringBootClassName(String name) {
+ // Classes inside BOOT-INF/classes will be loaded by the new
classloader as if they were in the root
+ if (name.startsWith(SPRING_BOOT_BOOT_INF_CLASSES_ROOT)) {
+ name = name.substring(SPRING_BOOT_BOOT_INF_CLASSES_ROOT.length());
+ }
+ if (name.startsWith(SPRING_BOOT_WEB_INF_CLASSES_ROOT)) {
+ name = name.substring(SPRING_BOOT_WEB_INF_CLASSES_ROOT.length());
+ }
+ return name;
+ }
+
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/fatjar/Main.java
b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/Main.java
new file mode 100644
index 000000000000..8760457a0a0b
--- /dev/null
+++ b/core/camel-main/src/main/java/org/apache/camel/main/fatjar/Main.java
@@ -0,0 +1,66 @@
+/*
+ * 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.camel.main.fatjar;
+
+import org.apache.camel.CamelConfiguration;
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.PackageScanClassResolver;
+import org.apache.camel.spi.PackageScanResourceResolver;
+
+/**
+ * A Main class for booting up Camel in standalone mode packaged as far-jar
using the camel-repackager-maven-plugin.
+ */
+public class Main extends org.apache.camel.main.Main {
+
+ /**
+ * Camel main application
+ *
+ * It is recommended to use {@link org.apache.camel.main.Main#Main(Class)}
to specify the main class.
+ */
+ public Main() {
+ }
+
+ /**
+ * Camel main application
+ *
+ * @param mainClass the main class
+ */
+ public Main(Class<?> mainClass) {
+ super(mainClass);
+ }
+
+ /**
+ * Camel main application
+ *
+ * @param mainClass the main class
+ * @param configurationClasses additional camel configuration classes
+ */
+ @SafeVarargs
+ public Main(Class<?> mainClass, Class<? extends CamelConfiguration>...
configurationClasses) {
+ super(mainClass, configurationClasses);
+ }
+
+ @Override
+ protected CamelContext createCamelContext() {
+ CamelContext context = super.createCamelContext();
+
context.getCamelContextExtension().addContextPlugin(PackageScanClassResolver.class,
+ new FatJarPackageScanClassResolver());
+
context.getCamelContextExtension().addContextPlugin(PackageScanResourceResolver.class,
+ new FatJarPackageScanResourceResolver());
+ return context;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-pom.tmpl
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-pom.tmpl
index 361e30aa519c..9c01813bd7e9 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-pom.tmpl
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main-pom.tmpl
@@ -96,30 +96,19 @@
</plugin>
<!-- package as runner jar -->
<plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <version>3.7.1</version>
- <configuration>
- <descriptors>
-
<descriptor>src/main/resources/assembly/runner.xml</descriptor>
- </descriptors>
- <archive>
- <manifest>
- <mainClass>{{ .MainClassname }}</mainClass>
- </manifest>
- <manifestEntries>
- <Multi-Release>true</Multi-Release>
- </manifestEntries>
- </archive>
- <appendAssemblyId>false</appendAssemblyId>
- </configuration>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-repackager-maven-plugin</artifactId>
+ <version>{{ .CamelVersion }}</version>
<executions>
<execution>
- <id>make-assembly</id>
+ <id>repackage-executable</id>
<phase>package</phase>
<goals>
- <goal>single</goal>
+ <goal>repackage</goal>
</goals>
+ <configuration>
+ <mainClass>{{ .MainClassname }}</mainClass>
+ </configuration>
</execution>
</executions>
</plugin>
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main.tmpl
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main.tmpl
index 52e141843ca6..edb65a0f7f13 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main.tmpl
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/templates/main.tmpl
@@ -1,6 +1,6 @@
{{ .PackageName }}
-import org.apache.camel.main.Main;
+import org.apache.camel.main.fatjar.Main;
public class {{ .MainClassname }} {