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 4e00dfea3b4d CAMEL-25836: camel-jte - compile a template given in the
CamelJteTemplate header once for its content (#28257)
4e00dfea3b4d is described below
commit 4e00dfea3b4da34d7cece88adadf4d1b1a19103a
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 23 02:18:59 2026 +0530
CAMEL-25836: camel-jte - compile a template given in the CamelJteTemplate
header once for its content (#28257)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/docs/jte-component.adoc | 3 +
.../camel-jte/src/main/docs/jte-component.adoc | 3 +
.../camel/component/jte/JteCodeResolver.java | 8 +-
.../apache/camel/component/jte/JteEndpoint.java | 22 ++++-
.../jte/JteTemplateFromHeaderCompiledOnceTest.java | 107 +++++++++++++++++++++
5 files changed, 139 insertions(+), 4 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jte-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jte-component.adoc
index b50239b7b203..931e4edb1549 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jte-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jte-component.adoc
@@ -84,6 +84,9 @@ location for a template or the template content itself. If
any of these
headers is set, then Camel uses this over the endpoint configured
resource. This allows you to provide a dynamic template at runtime.
+Each distinct template text from the `CamelJteTemplate` header is compiled
once, and the compiled template is kept
+until the component stops. So the templates in this header should come from a
small, known set.
+
== Examples
For example, you could use something like:
diff --git a/components/camel-jte/src/main/docs/jte-component.adoc
b/components/camel-jte/src/main/docs/jte-component.adoc
index b50239b7b203..931e4edb1549 100644
--- a/components/camel-jte/src/main/docs/jte-component.adoc
+++ b/components/camel-jte/src/main/docs/jte-component.adoc
@@ -84,6 +84,9 @@ location for a template or the template content itself. If
any of these
headers is set, then Camel uses this over the endpoint configured
resource. This allows you to provide a dynamic template at runtime.
+Each distinct template text from the `CamelJteTemplate` header is compiled
once, and the compiled template is kept
+until the component stops. So the templates in this header should come from a
small, known set.
+
== Examples
For example, you could use something like:
diff --git
a/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteCodeResolver.java
b/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteCodeResolver.java
index 96adda8c9186..35d1246935c4 100644
---
a/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteCodeResolver.java
+++
b/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteCodeResolver.java
@@ -39,9 +39,11 @@ public class JteCodeResolver implements CodeResolver {
this.camelContext = camelContext;
}
- public void addTemplateFromHeader(String exchangeId, String template) {
- if (exchangeId != null && template != null) {
- headerTemplates.put(exchangeId, template);
+ public void addTemplateFromHeader(String name, String template) {
+ if (name != null && template != null) {
+ // resolve removes the entry, but the engine resolves a name only
when it compiles it: after the first
+ // exchange with a template, its text stays here (one copy per
distinct template, as in the engine)
+ headerTemplates.put(name, template);
}
}
diff --git
a/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteEndpoint.java
b/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteEndpoint.java
index 2b237ef87fd4..6a7113daa693 100644
---
a/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteEndpoint.java
+++
b/components/camel-jte/src/main/java/org/apache/camel/component/jte/JteEndpoint.java
@@ -16,6 +16,11 @@
*/
package org.apache.camel.component.jte;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HexFormat;
+
import gg.jte.TemplateEngine;
import gg.jte.TemplateOutput;
import gg.jte.output.StringOutput;
@@ -114,7 +119,9 @@ public class JteEndpoint extends ResourceEndpoint {
exchange.getIn().removeHeader(JteConstants.JTE_TEMPLATE);
// add template in code resolver so we can find it
if (codeResolver != null) {
- name = exchange.getExchangeId();
+ // named after its content: the engine compiles each
template name into a class that it keeps,
+ // so a name per exchange would compile the same template
again for every exchange
+ name = headerTemplateName(content);
codeResolver.addTemplateFromHeader(name, content);
}
}
@@ -151,4 +158,17 @@ public class JteEndpoint extends ResourceEndpoint {
ExchangeHelper.setInOutBodyPatternAware(exchange, s);
}
+ /**
+ * The name of a template given in the {@link JteConstants#JTE_TEMPLATE}
header: the same for the same template
+ * content, and different for different contents.
+ */
+ static String headerTemplateName(String content) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ return "header-" +
HexFormat.of().formatHex(digest.digest(content.getBytes(StandardCharsets.UTF_8)));
+ } catch (NoSuchAlgorithmException e) {
+ // every Java platform supports SHA-256
+ throw new IllegalStateException(e);
+ }
+ }
}
diff --git
a/components/camel-jte/src/test/java/org/apache/camel/component/jte/JteTemplateFromHeaderCompiledOnceTest.java
b/components/camel-jte/src/test/java/org/apache/camel/component/jte/JteTemplateFromHeaderCompiledOnceTest.java
new file mode 100644
index 000000000000..2b5047e26bc0
--- /dev/null
+++
b/components/camel-jte/src/test/java/org/apache/camel/component/jte/JteTemplateFromHeaderCompiledOnceTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.component.jte;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.util.FileUtil;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A template given in the {@link JteConstants#JTE_TEMPLATE} header is
compiled once for its content, not once for every
+ * exchange: each compiled template is a class that the template engine keeps,
and a source and a class file in the work
+ * directory.
+ */
+class JteTemplateFromHeaderCompiledOnceTest extends CamelTestSupport {
+
+ private static final String WORK_DIR = "target/jte-header-compiled-once";
+
+ private static final String HELLO = """
+ @import org.apache.camel.component.jte.Model
+ @param Model model
+
+ Hello ${model.header("name")}.
+ """;
+
+ private static final String BYE = """
+ @import org.apache.camel.component.jte.Model
+ @param Model model
+
+ Bye ${model.header("name")}.
+ """;
+
+ @Override
+ protected void doPreSetup() throws Exception {
+ FileUtil.removeDir(Path.of(WORK_DIR).toFile());
+ }
+
+ @Test
+ void sameTemplateIsCompiledOnce() throws Exception {
+ for (int i = 0; i < 20; i++) {
+ assertEquals("Hello Scott" + i + ".", render(HELLO, "Scott" + i));
+ }
+
+ assertEquals(1, compiledTemplates(), "The template of the 20 exchanges
should have been compiled once");
+ }
+
+ @Test
+ void eachDistinctTemplateIsCompiledOnce() throws Exception {
+ for (int i = 0; i < 10; i++) {
+ assertEquals("Hello Scott" + i + ".", render(HELLO, "Scott" + i));
+ assertEquals("Bye Scott" + i + ".", render(BYE, "Scott" + i));
+ }
+
+ assertEquals(2, compiledTemplates(), "Each of the two templates should
have been compiled once");
+ }
+
+ private String render(String content, String name) throws Exception {
+ Exchange exchange = template.request("direct:a", e -> {
+ e.getIn().setHeader(JteConstants.JTE_TEMPLATE, content);
+ e.getIn().setHeader("name", name);
+ });
+ if (exchange.isFailed()) {
+ throw exchange.getException();
+ }
+ return exchange.getMessage().getBody(String.class);
+ }
+
+ private static long compiledTemplates() throws IOException {
+ try (Stream<Path> files = Files.walk(Path.of(WORK_DIR))) {
+ return files.filter(f ->
f.getFileName().toString().endsWith(".class")).count();
+ }
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ JteComponent jte = context.getComponent("jte",
JteComponent.class);
+ jte.setWorkDir(WORK_DIR);
+
+
from("direct:a").to("jte:org/apache/camel/component/jte/dummy.jte?allowTemplateFromHeader=true");
+ }
+ };
+ }
+}