This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-upgrade-recipes.git
The following commit(s) were added to refs/heads/main by this push:
new 35754b6 Add Camel 4.22.1 migration recipe for camel-openai
storeFullResponse property (CAMEL-24539)
35754b6 is described below
commit 35754b69cb0e65d94d985f708d7571913997e7ed
Author: Federico Mariani <[email protected]>
AuthorDate: Wed Sep 16 09:53:01 2026 +0200
Add Camel 4.22.1 migration recipe for camel-openai storeFullResponse
property (CAMEL-24539)
---
camel-upgrade-recipes/pom.xml | 8 +
.../FlagOpenAIFullResponsePropertyInJavaDsl.java | 130 +++++++++
.../FlagOpenAIFullResponsePropertyInXmlDsl.java | 129 +++++++++
.../FlagOpenAIFullResponsePropertyInYamlDsl.java | 112 ++++++++
.../main/resources/META-INF/rewrite/4.22.1.yaml | 59 ++++
.../org/apache/camel/upgrade/CamelTestUtil.java | 3 +-
.../apache/camel/upgrade/CamelUpdate422_1Test.java | 296 +++++++++++++++++++++
release_notes.adoc | 75 ++++++
8 files changed, 811 insertions(+), 1 deletion(-)
diff --git a/camel-upgrade-recipes/pom.xml b/camel-upgrade-recipes/pom.xml
index 72602f4..15c0876 100644
--- a/camel-upgrade-recipes/pom.xml
+++ b/camel-upgrade-recipes/pom.xml
@@ -635,6 +635,14 @@
<outputDirectory>${rewrite-tmp-classpath}</outputDirectory>
</artifactItem>
+ <!-- camel-openai for OpenAIConstants on the
classpath (4.22.1) -->
+ <artifactItem>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-openai</artifactId>
+ <version>4.22.0</version>
+
<outputDirectory>${rewrite-tmp-classpath}</outputDirectory>
+ </artifactItem>
+
</artifactItems>
</configuration>
</execution>
diff --git
a/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInJavaDsl.java
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInJavaDsl.java
new file mode 100644
index 0000000..e7daf61
--- /dev/null
+++
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInJavaDsl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.upgrade.camel422_1;
+
+import org.apache.camel.upgrade.AbstractCamelJavaVisitor;
+import org.apache.camel.upgrade.RecipesUtil;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+import org.openrewrite.java.tree.TypeUtils;
+
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+/**
+ * Flags camel-openai {@code CamelOpenAIResponse} / {@code
OpenAIConstants.RESPONSE} usages in Java
+ * DSL for manual review (CAMEL-24539): with {@code storeFullResponse=true},
embeddings,
+ * audio-transcription and audio-translation moved to their own exchange
property, while
+ * chat-completion is unchanged. Which one applies to a given occurrence is
not mechanically
+ * decidable in general (exchange properties cross route boundaries via error
handlers,
+ * {@code onCompletion}, and endpoints such as {@code direct:}), so this
recipe marks every
+ * occurrence with a comment instead of rewriting it.
+ * <p>
+ * Comments are attached only to literal/field-access argument nodes, never to
a method invocation
+ * that is itself part of a fluent chain: OpenRewrite prints an invocation's
own prefix before its
+ * whole {@code select} subtree, so a comment on a chain link renders before
the start of the chain
+ * rather than next to that link.
+ */
+public class FlagOpenAIFullResponsePropertyInJavaDsl extends Recipe {
+
+ static final String WARNING_COMMENT
+ = " CAMEL-24539 (Camel 4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation "
+ + "now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
"
+ + "instead of CamelOpenAIResponse; chat-completion is unchanged.
Verify which operation this refers to and update "
+ + "the property name manually if needed.";
+
+ @Override
+ public String getDisplayName() {
+ return "Flag camel-openai CamelOpenAIResponse property in Java DSL";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Adds a review comment wherever CamelOpenAIResponse or
OpenAIConstants.RESPONSE is used, since whether it " +
+ "should become
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
" +
+ "or stay CamelOpenAIResponse cannot be decided statically.";
+ }
+
+ @Override
+ public TreeVisitor<?, ExecutionContext> getVisitor() {
+ return RecipesUtil.newVisitor(new FlagVisitor());
+ }
+
+ private static class FlagVisitor extends AbstractCamelJavaVisitor {
+ private static final String OLD_PROPERTY_NAME = "CamelOpenAIResponse";
+ private static final String OPENAI_CONSTANTS_FQN =
"org.apache.camel.component.openai.OpenAIConstants";
+
+ // ${exchangeProperty.CamelOpenAIResponse},
${exchangeProperty[CamelOpenAIResponse]},
+ // ${exchangeProperty.CamelOpenAIResponse.someOgnl},
${exchangeProperty(CamelOpenAIResponse)},
+ // ${exchangePropertyAs(CamelOpenAIResponse, ...)} -- whole name only,
so CamelOpenAIResponseModel etc. don't match
+ private static final Pattern SIMPLE_EXCHANGE_PROPERTY =
Pattern.compile(
+
"\\$\\{exchangeProperty(?:\\.CamelOpenAIResponse[.}]|\\[CamelOpenAIResponse]}|(?:As)?\\(CamelOpenAIResponse[,)])");
+
+ @Override
+ protected J.FieldAccess doVisitFieldAccess(J.FieldAccess fieldAccess,
ExecutionContext ctx) {
+ J.FieldAccess fa = super.doVisitFieldAccess(fieldAccess, ctx);
+
+ if (referencesOpenAIConstantsResponse(fa) &&
!RecipesUtil.isCommentBeforeElement(fa, WARNING_COMMENT)) {
+ fa =
fa.withComments(Collections.singletonList(RecipesUtil.createMultinlineComment(WARNING_COMMENT)));
+ }
+
+ return fa;
+ }
+
+ @Override
+ protected J.Literal doVisitLiteral(J.Literal literal, ExecutionContext
ctx) {
+ J.Literal l = super.doVisitLiteral(literal, ctx);
+
+ if (!(l.getValue() instanceof String)) {
+ return l;
+ }
+ String value = (String) l.getValue();
+
+ boolean flag = OLD_PROPERTY_NAME.equals(value) ||
SIMPLE_EXCHANGE_PROPERTY.matcher(value).find();
+ if (flag && !RecipesUtil.isCommentBeforeElement(l,
WARNING_COMMENT)) {
+ l =
l.withComments(Collections.singletonList(RecipesUtil.createMultinlineComment(WARNING_COMMENT)));
+ }
+
+ return l;
+ }
+
+ /**
+ * True for {@code OpenAIConstants.RESPONSE}. The type may not be
attributed (camel-openai need not
+ * be on the recipe's own classpath), so this falls back to the class
import when there is no type.
+ */
+ private boolean referencesOpenAIConstantsResponse(J.FieldAccess fa) {
+ if (!"RESPONSE".equals(fa.getSimpleName()) || !(fa.getTarget()
instanceof J.Identifier)) {
+ return false;
+ }
+
+ JavaType targetType = fa.getTarget().getType();
+ if (targetType != null) {
+ return TypeUtils.isOfClassType(targetType,
OPENAI_CONSTANTS_FQN);
+ }
+
+ if (!"OpenAIConstants".equals(((J.Identifier)
fa.getTarget()).getSimpleName())) {
+ return false;
+ }
+ J.CompilationUnit cu =
getCursor().firstEnclosing(J.CompilationUnit.class);
+ return cu != null && cu.getImports().stream()
+ .anyMatch(imp -> !imp.isStatic() &&
OPENAI_CONSTANTS_FQN.equals(imp.getTypeName()));
+ }
+ }
+}
diff --git
a/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInXmlDsl.java
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInXmlDsl.java
new file mode 100644
index 0000000..6008be2
--- /dev/null
+++
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInXmlDsl.java
@@ -0,0 +1,129 @@
+/*
+ * 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.upgrade.camel422_1;
+
+import org.apache.camel.upgrade.AbstractCamelXmlVisitor;
+import org.apache.camel.upgrade.RecipesUtil;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.xml.tree.Content;
+import org.openrewrite.xml.tree.Xml;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static
org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInJavaDsl.WARNING_COMMENT;
+
+/**
+ * Flags camel-openai {@code CamelOpenAIResponse} usages in XML DSL for manual
review (CAMEL-24539).
+ * See {@link FlagOpenAIFullResponsePropertyInJavaDsl} for why this marks
instead of rewriting.
+ * <p>
+ * A comment is inserted as a real {@code Xml.Comment} sibling in the parent's
content, right before
+ * the flagged tag, rather than as raw {@code <!--...-->} text glued into the
tag's prefix: on reparse
+ * (e.g. a second recipe run) a prefix-embedded comment turns into its own
sibling {@code Xml.Comment}
+ * node, so a prefix-string-based idempotency check would stop seeing it and
duplicate the comment.
+ */
+public class FlagOpenAIFullResponsePropertyInXmlDsl extends Recipe {
+
+ // A short, unique substring of WARNING_COMMENT used to detect an
already-added comment
+ private static final String MARKER = "CAMEL-24539";
+
+ private static final String OLD_PROPERTY_NAME = "CamelOpenAIResponse";
+
+ private static final Pattern SIMPLE_EXCHANGE_PROPERTY = Pattern.compile(
+
"\\$\\{exchangeProperty(?:\\.CamelOpenAIResponse[.}]|\\[CamelOpenAIResponse]}|(?:As)?\\(CamelOpenAIResponse[,)])");
+
+ @Override
+ public String getDisplayName() {
+ return "Flag camel-openai CamelOpenAIResponse property in XML DSL";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Adds a review comment wherever CamelOpenAIResponse is used,
since whether it should become " +
+
"CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
" +
+ "or stay CamelOpenAIResponse cannot be decided statically.";
+ }
+
+ @Override
+ public TreeVisitor<?, ExecutionContext> getVisitor() {
+ return Preconditions.check(RecipesUtil.camelXmlDslPrecondition(), new
FlagVisitor());
+ }
+
+ private static class FlagVisitor extends AbstractCamelXmlVisitor {
+
+ @Override
+ public Xml.Tag doVisitTag(Xml.Tag tag, ExecutionContext ctx) {
+ Xml.Tag t = super.doVisitTag(tag, ctx);
+
+ List<? extends Content> content = t.getContent();
+ if (content == null || content.isEmpty()) {
+ return t;
+ }
+
+ List<Content> newContent = null;
+ for (int i = 0; i < content.size(); i++) {
+ Content c = content.get(i);
+ if (c instanceof Xml.Tag && shouldFlag((Xml.Tag) c) &&
!hasPrecedingMarkerComment(content, i)) {
+ if (newContent == null) {
+ newContent = new ArrayList<>(content.subList(0, i));
+ }
+
newContent.add(RecipesUtil.createXmlComment(WARNING_COMMENT).withPrefix(c.getPrefix()));
+ }
+ if (newContent != null) {
+ newContent.add(c);
+ }
+ }
+
+ return newContent != null ? t.withContent(newContent) : t;
+ }
+
+ private static boolean hasPrecedingMarkerComment(List<? extends
Content> content, int index) {
+ return index > 0 && content.get(index - 1) instanceof Xml.Comment
+ && ((Xml.Comment) content.get(index -
1)).getText().contains(MARKER);
+ }
+
+ private static boolean shouldFlag(Xml.Tag tag) {
+ boolean propertyTag = "setProperty".equals(tag.getName()) ||
"removeProperty".equals(tag.getName());
+
+ for (Xml.Attribute attr : tag.getAttributes()) {
+ String value = attr.getValueAsString();
+ if (value == null) {
+ continue;
+ }
+ if (propertyTag && "name".equals(attr.getKeyAsString()) &&
OLD_PROPERTY_NAME.equals(value)) {
+ return true;
+ }
+ if (SIMPLE_EXCHANGE_PROPERTY.matcher(value).find()) {
+ return true;
+ }
+ }
+
+ if (tag.getChildren().isEmpty()) {
+ String value = tag.getValue().orElse(null);
+ if (value != null &&
SIMPLE_EXCHANGE_PROPERTY.matcher(value).find()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git
a/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInYamlDsl.java
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInYamlDsl.java
new file mode 100644
index 0000000..76ff6b9
--- /dev/null
+++
b/camel-upgrade-recipes/src/main/java/org/apache/camel/upgrade/camel422_1/FlagOpenAIFullResponsePropertyInYamlDsl.java
@@ -0,0 +1,112 @@
+/*
+ * 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.upgrade.camel422_1;
+
+import org.apache.camel.upgrade.AbstractCamelYamlVisitor;
+import org.apache.camel.upgrade.RecipesUtil;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.yaml.tree.Yaml;
+
+import java.util.regex.Pattern;
+
+import static
org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInJavaDsl.WARNING_COMMENT;
+
+/**
+ * Flags camel-openai {@code CamelOpenAIResponse} usages in YAML DSL for
manual review (CAMEL-24539).
+ * See {@link FlagOpenAIFullResponsePropertyInJavaDsl} for why this marks
instead of rewriting.
+ */
+public class FlagOpenAIFullResponsePropertyInYamlDsl extends Recipe {
+
+ // A short, unique substring of WARNING_COMMENT used to detect an
already-added comment
+ private static final String MARKER = "CAMEL-24539";
+
+ private static final String OLD_PROPERTY_NAME = "CamelOpenAIResponse";
+
+ private static final Pattern SIMPLE_EXCHANGE_PROPERTY = Pattern.compile(
+
"\\$\\{exchangeProperty(?:\\.CamelOpenAIResponse[.}]|\\[CamelOpenAIResponse]}|(?:As)?\\(CamelOpenAIResponse[,)])");
+
+ @Override
+ public String getDisplayName() {
+ return "Flag camel-openai CamelOpenAIResponse property in YAML DSL";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Adds a review comment wherever CamelOpenAIResponse is used,
since whether it should become " +
+
"CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
" +
+ "or stay CamelOpenAIResponse cannot be decided statically.";
+ }
+
+ @Override
+ public TreeVisitor<?, ExecutionContext> getVisitor() {
+ return Preconditions.check(RecipesUtil.camelYamlDslPrecondition(), new
FlagVisitor());
+ }
+
+ private static class FlagVisitor extends AbstractCamelYamlVisitor {
+
+ @Override
+ protected void clearLocalCache() {
+ // Nothing to clear
+ }
+
+ @Override
+ public Yaml.Mapping.Entry doVisitMappingEntry(Yaml.Mapping.Entry
entry, ExecutionContext ctx) {
+ Yaml.Mapping.Entry e = super.doVisitMappingEntry(entry, ctx);
+
+ if (!(e.getValue() instanceof Yaml.Scalar)) {
+ return e;
+ }
+ Yaml.Scalar scalar = (Yaml.Scalar) e.getValue();
+
+ boolean flag = ("name".equals(e.getKey().getValue()) &&
OLD_PROPERTY_NAME.equals(scalar.getValue())
+ && isPropertyOperation())
+ ||
SIMPLE_EXCHANGE_PROPERTY.matcher(scalar.getValue()).find();
+
+ if (flag && !e.getPrefix().contains(MARKER)) {
+ return e.withPrefix(addCommentToPrefix(e.getPrefix()));
+ }
+
+ return e;
+ }
+
+ /**
+ * Check if this entry is within a setProperty or removeProperty
mapping.
+ */
+ private boolean isPropertyOperation() {
+ for (int i = 0; i < 10; i++) {
+ Object value = getCursor().getParent(i) != null ?
getCursor().getParent(i).getValue() : null;
+ if (value instanceof Yaml.Mapping) {
+ boolean hasPropertyOp = ((Yaml.Mapping)
value).getEntries().stream()
+ .anyMatch(e ->
"setProperty".equals(e.getKey().getValue()) ||
"removeProperty".equals(e.getKey().getValue()));
+ if (hasPropertyOp) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static String addCommentToPrefix(String prefix) {
+ int lastNewline = prefix.lastIndexOf('\n');
+ String indent = lastNewline >= 0 ? prefix.substring(lastNewline +
1) : "";
+ return prefix + "#" + WARNING_COMMENT + "\n" + indent;
+ }
+ }
+}
diff --git
a/camel-upgrade-recipes/src/main/resources/META-INF/rewrite/4.22.1.yaml
b/camel-upgrade-recipes/src/main/resources/META-INF/rewrite/4.22.1.yaml
new file mode 100644
index 0000000..9fe21c5
--- /dev/null
+++ b/camel-upgrade-recipes/src/main/resources/META-INF/rewrite/4.22.1.yaml
@@ -0,0 +1,59 @@
+#
+# 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.
+#
+
+#####
+# Rules coming from
https://camel.apache.org/manual/camel-4x-upgrade-guide-4_22.html
+# Specifically changes introduced in 4.22.1 (upgrading from 4.22.0 to 4.22.1)
+#
+# This patch is not yet referenced from any other recipe: there is no
4.23.yaml on this
+# branch to chain it from (see RECIPE-CONVENTIONS.md), and the 4.22 LTS
aggregate is tracked
+# separately (https://github.com/apache/camel-upgrade-recipes/issues/129). Run
it directly
+# with
-Drewrite.activeRecipes=org.apache.camel.upgrade.camel422_1.CamelMigrationRecipe
until
+# one of those exists.
+#####
+
+#####
+# Update the Camel project from 4.22.0 to 4.22.1
+#####
+---
+type: specs.openrewrite.org/v1beta/recipe
+name: org.apache.camel.upgrade.camel422_1.CamelMigrationRecipe
+displayName: Migrates `camel 4.22.0` application to `camel 4.22.1`
+description: Migrates `camel 4.22.0` application to `camel 4.22.1`.
+recipeList:
+ - org.apache.camel.upgrade.camel422_1.flagOpenAIFullResponseProperty
+---
+##
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#camel-openai-1
+type: specs.openrewrite.org/v1beta/recipe
+name: org.apache.camel.upgrade.camel422_1.flagOpenAIFullResponseProperty
+displayName: Flag camel-openai CamelOpenAIResponse property for manual review
(CAMEL-24539)
+description: >-
+ With storeFullResponse=true, the embeddings, audio-transcription and
audio-translation camel-openai
+ operations now store their full response under an operation-specific
exchange property instead of the
+ shared CamelOpenAIResponse property (chat-completion is unchanged). Which
one applies to a given
+ CamelOpenAIResponse/OpenAIConstants.RESPONSE occurrence is not mechanically
decidable in general -
+ exchange properties cross route boundaries via error handlers, onCompletion
and endpoints such as
+ direct: - so this recipe adds a review comment at every occurrence instead
of rewriting it. Only
+ applies if the camel-openai dependency is present.
+preconditions:
+ - org.openrewrite.java.dependencies.search.ModuleHasDependency:
+ groupIdPattern: org.apache.camel
+ artifactIdPattern: camel-openai
+recipeList:
+ - org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInJavaDsl
+ - org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInXmlDsl
+ - org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInYamlDsl
diff --git
a/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelTestUtil.java
b/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelTestUtil.java
index 7118070..32df9e4 100644
---
a/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelTestUtil.java
+++
b/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelTestUtil.java
@@ -63,7 +63,8 @@ public class CamelTestUtil {
v4_19(4, 19, 0),
v4_20(4, 20, 0),
v4_21(4, 21, 0),
- v4_22(4, 22, 0);
+ v4_22(4, 22, 0),
+ v4_22_1(4, 22, 1, true);
private int major;
private int minor;
diff --git
a/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelUpdate422_1Test.java
b/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelUpdate422_1Test.java
new file mode 100644
index 0000000..16c2151
--- /dev/null
+++
b/camel-upgrade-recipes/src/test/java/org/apache/camel/upgrade/CamelUpdate422_1Test.java
@@ -0,0 +1,296 @@
+/*
+ * 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.upgrade;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+import org.openrewrite.test.TypeValidation;
+
+import static org.openrewrite.java.Assertions.java;
+import static org.openrewrite.java.Assertions.mavenProject;
+import static org.openrewrite.xml.Assertions.xml;
+import static org.openrewrite.yaml.Assertions.yaml;
+
+/**
+ * Tests for migrating from Camel 4.22.0 to 4.22.1 (CAMEL-24539, camel-openai).
+ * <p>
+ * Whether a given CamelOpenAIResponse occurrence should become the
embeddings/audio-transcription/
+ * audio-translation property, or stay CamelOpenAIResponse (chat-completion),
is not mechanically
+ * decidable in general -- see {@code
org.apache.camel.upgrade.camel422_1.FlagOpenAIFullResponsePropertyInJavaDsl}
+ * for why. So the recipe flags every occurrence with a review comment instead
of rewriting it.
+ */
+public class CamelUpdate422_1Test implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ CamelTestUtil.recipe(spec, CamelTestUtil.CamelVersion.v4_22_1)
+
.parser(CamelTestUtil.parserFromClasspath(CamelTestUtil.CamelVersion.v4_22,
"camel-api",
+ "camel-core-model", "camel-support", "camel-openai"))
+ .typeValidationOptions(TypeValidation.none());
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void javaDslFlagsOccurrencesAndLeavesLookalikesAndUnrelatedConstantAlone()
{
+ //language=java
+ rewriteRun(
+ mavenProject("test-openai",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ java(
+ """
+ import org.apache.camel.builder.RouteBuilder;
+ import
org.apache.camel.component.openai.OpenAIConstants;
+
+ class Test extends RouteBuilder {
+ public void configure() {
+ from("direct:openai")
+ .setProperty("CamelOpenAIResponseModel",
constant("keep-model"))
+ .process(exchange -> {
+ Object response =
exchange.getProperty("CamelOpenAIResponse", Object.class);
+
exchange.setProperty("CamelOpenAIResponse", response);
+ })
+ .setProperty(OpenAIConstants.RESPONSE,
exchangeProperty("CamelOpenAIResponse"))
+
.setBody(simple("${exchangeProperty.CamelOpenAIResponse.data}"))
+ .removeProperty("CamelOpenAIResponseId")
+ .removeProperty("CamelOpenAIResponse");
+ }
+ }
+ """,
+ """
+ import org.apache.camel.builder.RouteBuilder;
+ import
org.apache.camel.component.openai.OpenAIConstants;
+
+ class Test extends RouteBuilder {
+ public void configure() {
+ from("direct:openai")
+ .setProperty("CamelOpenAIResponseModel",
constant("keep-model"))
+ .process(exchange -> {
+ Object response =
exchange.getProperty(/* CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/"CamelOpenAIResponse", Object.class);
+ exchange.setProperty(/* CAMEL-24539
(Camel 4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/"CamelOpenAIResponse", response);
+ })
+ .setProperty(/* CAMEL-24539 (Camel
4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/OpenAIConstants.RESPONSE, exchangeProperty(/* CAMEL-24539 (Camel
4.22.1) [...]
+ .setBody(simple(/* CAMEL-24539 (Camel
4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/"${exchangeProperty.CamelOpenAIResponse.data}"))
+ .removeProperty("CamelOpenAIResponseId")
+ .removeProperty(/* CAMEL-24539 (Camel
4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/"CamelOpenAIResponse");
+ }
+ }
+ """
+ ),
+ // A user's own unrelated OpenAIConstants (no
camel-openai import) must never be flagged.
+ java(
+ """
+ package com.example;
+
+ public final class OpenAIConstants {
+ public static final String RESPONSE =
"SomeUnrelatedConstant";
+ }
+ """
+ ),
+ java(
+ """
+ package com.example;
+
+ import org.apache.camel.builder.RouteBuilder;
+
+ class UnrelatedRoute extends RouteBuilder {
+ public void configure() {
+ from("direct:other")
+ .setProperty(OpenAIConstants.RESPONSE,
constant("x"));
+ }
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void javaDslWithoutCamelOpenAIDependencyIsUnchanged() {
+ //language=java
+ rewriteRun(
+ mavenProject("test-no-openai",
+ CamelTestUtil.pomXmlSpec("camel-core",
CamelTestUtil.CamelVersion.v4_22),
+ java(
+ """
+ import org.apache.camel.builder.RouteBuilder;
+
+ class Test extends RouteBuilder {
+ public void configure() {
+ from("direct:openai")
+ .setProperty("CamelOpenAIResponse",
exchangeProperty("CamelOpenAIResponse"));
+ }
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void javaDslIsIdempotent() {
+ //language=java
+ rewriteRun(
+ mavenProject("test-openai-idempotent",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ java(
+ """
+ import org.apache.camel.builder.RouteBuilder;
+
+ class Test extends RouteBuilder {
+ public void configure() {
+ from("direct:openai")
+ .setProperty(/* CAMEL-24539 (Camel
4.22.1): with storeFullResponse=true,
embeddings/audio-transcription/audio-translation now use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if
needed.*/"CamelOpenAIResponse", exchangeProperty(/* CAMEL-24539 (Camel 4.22.1):
w [...]
+ }
+ }
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void yamlDslFlagsOccurrencesAndLeavesLookalikesAlone() {
+ //language=yaml
+ rewriteRun(
+ mavenProject("test-openai-yaml",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ yaml(
+ """
+ - route:
+ from:
+ uri: "direct:openai"
+ steps:
+ - setProperty:
+ name: CamelOpenAIResponseModel
+ constant: keep-model
+ - setProperty:
+ name: CamelOpenAIResponse
+ simple:
"${exchangeProperty.CamelOpenAIResponse}"
+ - setBody:
+ simple:
"${exchangeProperty.CamelOpenAIResponse.data}"
+ """,
+ """
+ - route:
+ from:
+ uri: "direct:openai"
+ steps:
+ - setProperty:
+ name: CamelOpenAIResponseModel
+ constant: keep-model
+ - setProperty:
+ # CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.
+ name: CamelOpenAIResponse
+ # CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.
+ simple:
"${exchangeProperty.CamelOpenAIResponse}"
+ - setBody:
+ # CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.
+ simple:
"${exchangeProperty.CamelOpenAIResponse.data}"
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void yamlDslIsIdempotent() {
+ //language=yaml
+ rewriteRun(
+ mavenProject("test-openai-yaml-idempotent",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ yaml(
+ """
+ - route:
+ from:
+ uri: "direct:openai"
+ steps:
+ - setProperty:
+ # CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.
+ name: CamelOpenAIResponse
+ # CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.
+ simple:
"${exchangeProperty.CamelOpenAIResponse}"
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void xmlDslFlagsOccurrencesAndLeavesLookalikesAlone() {
+ //language=xml
+ rewriteRun(
+ mavenProject("test-openai-xml",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ xml(
+ """
+ <route xmlns="http://camel.apache.org/schema/spring">
+ <from uri="direct:openai"/>
+ <setProperty name="CamelOpenAIResponseModel">
+ <constant>keep-model</constant>
+ </setProperty>
+ <setProperty name="CamelOpenAIResponse">
+
<simple>${exchangeProperty.CamelOpenAIResponse}</simple>
+ </setProperty>
+ </route>
+ """,
+ """
+ <route xmlns="http://camel.apache.org/schema/spring">
+ <from uri="direct:openai"/>
+ <setProperty name="CamelOpenAIResponseModel">
+ <constant>keep-model</constant>
+ </setProperty>
+ <!-- CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.-->
+ <setProperty name="CamelOpenAIResponse">
+ <!-- CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.-->
+
<simple>${exchangeProperty.CamelOpenAIResponse}</simple>
+ </setProperty>
+ </route>
+ """
+ )
+ )
+ );
+ }
+
+ @Test
+ @DisabledIfSystemProperty(named = CamelTestUtil.PROPERTY_USE_RECIPE,
matches = ".+")
+ void xmlDslIsIdempotent() {
+ //language=xml
+ rewriteRun(
+ mavenProject("test-openai-xml-idempotent",
+ CamelTestUtil.pomXmlSpec("camel-openai",
CamelTestUtil.CamelVersion.v4_22),
+ xml(
+ """
+ <route xmlns="http://camel.apache.org/schema/spring">
+ <from uri="direct:openai"/>
+ <!-- CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.-->
+ <setProperty name="CamelOpenAIResponse">
+ <!-- CAMEL-24539 (Camel 4.22.1): with
storeFullResponse=true, embeddings/audio-transcription/audio-translation now
use
CamelOpenAIEmbeddingsResponse/CamelOpenAIAudioTranscriptionResponse/CamelOpenAIAudioTranslationResponse
instead of CamelOpenAIResponse; chat-completion is unchanged. Verify which
operation this refers to and update the property name manually if needed.-->
+
<simple>${exchangeProperty.CamelOpenAIResponse}</simple>
+ </setProperty>
+ </route>
+ """
+ )
+ )
+ );
+ }
+}
diff --git a/release_notes.adoc b/release_notes.adoc
index d523aa6..f0af1bd 100644
--- a/release_notes.adoc
+++ b/release_notes.adoc
@@ -1,5 +1,80 @@
= Camel Upgrade Recipes - Release notes
+== 4.22.1
+
+=== Summary
+
+This is a release supporting Camel, Camel Quarkus and Camel Spring Boot 4.22.1
LTS with the required upgrade recipes (described by the
https://camel.apache.org/manual/camel-4x-upgrade-guide-4_22.html[migration
guide]).
+
+This recipe is not yet chained from `latest.yaml` or a Spring Boot
`CamelMigrationRecipe`: per `RECIPE-CONVENTIONS.md`, a patch recipe is
referenced from the *next main-version* yaml and, optionally, an LTS aggregate,
and neither exists yet for the 4.22 line (a 4.22 LTS aggregate is tracked in
https://github.com/apache/camel-upgrade-recipes/issues/129[#129]). Run it
directly with
`-Drewrite.activeRecipes=org.apache.camel.upgrade.camel422_1.CamelMigrationRecipe`
until one of those exists.
+
+Only one 4.22.1 change is covered at all: the camel-openai `storeFullResponse`
property change (CAMEL-24539), and it cannot be migrated mechanically -- see
below. Everything else backported into 4.22.1 is either a
secure-default/runtime behaviour change or a deprecation with no replacement
yet, and needs manual review.
+
+=== Migration coverage
+
+The following table lists migration topics and indicates the level of coverage
(full, partial, or not covered), along with additional comments.
+
+[%autowidth,stripes=hover]
+|===
+| Migration | Coverage | Comment
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#camel-openai-1[camel-openai:
`storeFullResponse` properties]
+| ⚠️ Partial
+| Which of
`CamelOpenAIEmbeddingsResponse`/`CamelOpenAIAudioTranscriptionResponse`/`CamelOpenAIAudioTranslationResponse`
(or unchanged `CamelOpenAIResponse` for chat-completion) an occurrence should
become is not mechanically decidable -- exchange properties cross route
boundaries through error handlers, `onCompletion` and endpoints such as
`direct:`. Rather than risk a wrong rewrite, every `CamelOpenAIResponse` /
`OpenAIConstants.RESPONSE` occurrence (and the matching Simple-syntax form [...]
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L16[camel-hazelcast:
ReplicatedHazelcastAggregationRepository applies the default serialization
filter]
+| ❌ None
+| Runtime security default now also applied to the repository's
self-bootstrapped `HazelcastInstance`/`ClientConfig`. Applications aggregating
classes outside the default allow-list without supplying their own
`hazelcastInstance`/`ClientConfig` must provide a
`JavaSerializationFilterConfig`. Manual review required.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L36[camel-exec:
`allowControlHeaders` is `insecure:dev`]
+| ❌ None
+| `allowControlHeaders=true` now fails to start under
`camel.main.profile=prod` unless `camel.security.insecureDevPolicy` is relaxed.
Manual configuration required.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L48[camel-azure-storage-blob/datalake
and camel-google-storage: download symlink containment]
+| ❌ None
+| Local downloads through a symbolic link resolving outside the configured
directory (`fileDir` / `downloadFileName`) are now rejected as a security fix;
legitimate nested paths keep working. No migration needed.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L62[camel-jgroups:
consumer deserialization filter]
+| ❌ None
+| The consumer now applies a JEP-290 `java.io.ObjectInputFilter` (the shared
Camel default allow-list, or `jdk.serialFilter` when set) to the deserialized
message body. Routes exchanging custom classes over the cluster must widen it
with the new `deserializationFilter` endpoint option. Manual configuration
required.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L89[camel-tika:
`tikaConfig` deprecation]
+| ❌ None
+| `tikaConfig`/`tikaConfigUri` deprecated for removal in Camel 4.23 (Tika 4
drops `TikaConfig` and XML configuration). No replacement option exists yet, so
no migration is possible until 4.23.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1829[camel-support:
`BackgroundTask` cancellation]
+| ❌ None
+| `BackgroundTask.schedule`'s returned `Future` now correctly reports
`isCancelled()==true` once the task completes, instead of the repeating
schedule staying armed indefinitely. Behaviour fix, no migration needed.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1837[camel-master:
`backOffMaxAttempts`]
+| ❌ None
+| `backOffMaxAttempts` now correctly bounds consumer start retries instead of
being cut short by the default five second budget. Runtime behaviour fix, no
migration needed.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1844[camel-core:
REST client request validation and binary bodies]
+| ❌ None
+| With `clientRequestValidation` enabled, a required REST body is no longer
coerced to `String`, so binary bodies (e.g. `application/octet-stream`) keep
their original type. Bug fix, no migration needed.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1851[camel-infinispan:
aggregation repository recovery]
+| ❌ None
+| `InfinispanAggregationRepository` now keeps completed-but-unconfirmed
exchanges available for recovery instead of losing them; recovery now correctly
targets exchanges that failed after completion rather than aggregations still
in progress. Runtime behaviour fix, no migration needed.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1869[camel-servlet/camel-jetty:
multipart upload whitelist and attachment header]
+| ❌ None
+| `fileNameExtWhitelist` is now checked against the submitted file name
instead of the multipart field name (`camel-servlet`), and is now enforced at
all (`camel-jetty`); the `camel-jetty` attachment header is now exposed under
the multipart field name. Security fix — review the configured extension list
before upgrading.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1895[camel-vertx-http:
REST producer outbound header filter]
+| ❌ None
+| The REST producer now actually applies its outbound header filter (it
previously consulted the inbound filter), so common HTTP headers such as
`Content-Length`/`Host`/`Connection` are no longer copied onto the outgoing
request unless configured. Manual review required if a route relied on one of
those headers reaching the wire.
+
+|
https://github.com/apache/camel/blob/camel-4.22.1/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc#L1914[camel-tensorflow-serving:
`Target`/`Credentials` headers deprecated]
+| ❌ None
+| `CamelTensorFlowServingTarget`/`CamelTensorFlowServingCredentials` are
deprecated but were already never read by the component (the gRPC channel is
built once from the endpoint configuration), so no migration is needed — use
the `target`/`credentials` endpoint options instead.
+
+|
https://github.com/apache/camel/blob/main/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc[Security-default
backports documented only in the 4.23 guide]
+| ❌ None
+| Runtime/secure-default changes already effective as of 4.22.1 but only
written up in the (unreleased) 4.23 guide: crypto-pgp
`requireIntegrityProtection`, mllp `logPhi`, knative trust-all/`muteException`,
mina/cxf/grpc `muteException`, http credential scope and OAuth2 token cache,
http placeholders in `CamelHttpUri`, jetty/vertx CORS credentials,
platform-http proxy exact path and header echo, netty-http security constraint,
oauth `state`, shiro, keycloak issuer, xpath DOCTYPE, tika me [...]
+|===
+
== 4.22.0
=== Summary