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");
+            }
+        };
+    }
+}

Reply via email to