You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/01/27 10:24:22 UTC

[camel] 01/02: CAMEL-16082: Fix Java DSL expression vs predicate when using predicates via ExpressionClause (delegate) making it harder to know if it was a expression or predicate. So introducing PredicateFactory/Aware and initPredicate to handle predicates differently than expressions.

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 822ba0794181da3d440e141eba9b5317e0795e01
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 27 10:58:19 2021 +0100

    CAMEL-16082: Fix Java DSL expression vs predicate when using predicates via ExpressionClause (delegate) making it harder to know if it was a expression or predicate. So introducing PredicateFactory/Aware and initPredicate to handle predicates differently than expressions.
---
 .../src/main/java/org/apache/camel/Predicate.java  |   9 ++
 .../java/org/apache/camel/PredicateFactory.java    |  33 ++++++
 .../apache/camel/spi/PredicateFactoryAware.java    |  31 ++++++
 .../org/apache/camel/builder/ExpressionClause.java |  22 +++-
 .../camel/builder/ExpressionClauseSupport.java     |  72 +++++++-----
 .../camel/model/language/ExpressionDefinition.java |  16 ++-
 .../apache/camel/processor/ChoiceProcessor.java    |   2 +-
 .../camel/reifier/language/ExpressionReifier.java  |   4 +-
 .../camel/processor/ChoiceNoOtherwiseTest.java     | 122 +++++++++++++++++++++
 9 files changed, 281 insertions(+), 30 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/Predicate.java b/core/camel-api/src/main/java/org/apache/camel/Predicate.java
index b8bc9ed..851ffc5 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Predicate.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Predicate.java
@@ -42,4 +42,13 @@ public interface Predicate {
     default void init(CamelContext context) {
     }
 
+    /**
+     * Initialize as a predicate with the given camel context
+     *
+     * @param context the camel context
+     */
+    default void initPredicate(CamelContext context) {
+        init(context);
+    }
+
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/PredicateFactory.java b/core/camel-api/src/main/java/org/apache/camel/PredicateFactory.java
new file mode 100644
index 0000000..d8c4fd7
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/PredicateFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * A factory for creating {@link Predicate}
+ */
+@FunctionalInterface
+public interface PredicateFactory {
+
+    /**
+     * Creates a predicate
+     *
+     * @param  camelContext the camel context
+     * @return              the created predicate.
+     */
+    Predicate createPredicate(CamelContext camelContext);
+
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PredicateFactoryAware.java b/core/camel-api/src/main/java/org/apache/camel/spi/PredicateFactoryAware.java
new file mode 100644
index 0000000..5a6e5d0
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PredicateFactoryAware.java
@@ -0,0 +1,31 @@
+/*
+ * 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.spi;
+
+import org.apache.camel.PredicateFactory;
+
+/**
+ * When an instance is aware of {@link org.apache.camel.PredicateFactory}.
+ */
+public interface PredicateFactoryAware {
+
+    /**
+     * Gets the {@link PredicateFactory}.
+     */
+    PredicateFactory getPredicateFactory();
+
+}
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
index b8bdbc5..1e002ef 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
@@ -38,6 +38,7 @@ import org.apache.camel.support.builder.Namespaces;
 public class ExpressionClause<T> implements Expression, Predicate {
     private ExpressionClauseSupport<T> delegate;
     private volatile Expression expr;
+    private volatile Predicate pred;
 
     public ExpressionClause(T result) {
         this.delegate = new ExpressionClauseSupport<>(result);
@@ -998,6 +999,23 @@ public class ExpressionClause<T> implements Expression, Predicate {
     }
 
     @Override
+    public void initPredicate(CamelContext context) {
+        if (pred == null) {
+            synchronized (this) {
+                if (pred == null) {
+                    Expression newExpression = getExpressionValue();
+                    if (newExpression == null) {
+                        pred = delegate.getPredicateType().createPredicate(context);
+                    } else {
+                        pred = ExpressionToPredicateAdapter.toPredicate(newExpression);
+                    }
+                    pred.initPredicate(context);
+                }
+            }
+        }
+    }
+
+    @Override
     public <T> T evaluate(Exchange exchange, Class<T> type) {
         init(exchange.getContext());
         return expr.evaluate(exchange, type);
@@ -1005,7 +1023,7 @@ public class ExpressionClause<T> implements Expression, Predicate {
 
     @Override
     public boolean matches(Exchange exchange) {
-        init(exchange.getContext());
-        return new ExpressionToPredicateAdapter(expr).matches(exchange);
+        initPredicate(exchange.getContext());
+        return pred.matches(exchange);
     }
 }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index 2c84ab2..382b460 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -22,6 +22,7 @@ import org.apache.camel.BeanScope;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.ExpressionFactory;
+import org.apache.camel.PredicateFactory;
 import org.apache.camel.model.language.CSimpleExpression;
 import org.apache.camel.model.language.ConstantExpression;
 import org.apache.camel.model.language.DatasonnetExpression;
@@ -43,12 +44,13 @@ import org.apache.camel.model.language.XMLTokenizerExpression;
 import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
 import org.apache.camel.spi.ExpressionFactoryAware;
+import org.apache.camel.spi.PredicateFactoryAware;
 import org.apache.camel.support.builder.Namespaces;
 
 /**
  * A support class for building expression clauses.
  */
-public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
+public class ExpressionClauseSupport<T> implements ExpressionFactoryAware, PredicateFactoryAware {
 
     // Implementation detail: We must use the specific model.language.xxx
     // classes to make the DSL use these specific types
@@ -58,6 +60,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     private T result;
     private Expression expressionValue;
     private ExpressionFactory expressionType;
+    private PredicateFactory predicateType;
 
     public ExpressionClauseSupport(T result) {
         this.result = result;
@@ -70,8 +73,14 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
      * Specify an {@link org.apache.camel.Expression} instance
      */
     public T expression(Expression expression) {
-        if (expression instanceof ExpressionFactory) {
-            setExpressionType((ExpressionFactory) expression);
+        if (expression instanceof ExpressionFactory || expression instanceof PredicateFactory) {
+            // it can be both an expression and predicate
+            if (expression instanceof ExpressionFactory) {
+                setExpressionType((ExpressionFactory) expression);
+            }
+            if (expression instanceof PredicateFactory) {
+                setPredicateType((PredicateFactory) expression);
+            }
         } else {
             setExpressionValue(expression);
         }
@@ -384,7 +393,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T jsonpath(String text, Class<?> resultType) {
         JsonPathExpression expression = new JsonPathExpression(text);
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -400,7 +409,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         JsonPathExpression expression = new JsonPathExpression(text);
         expression.setSuppressExceptions(Boolean.toString(suppressExceptions));
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -418,7 +427,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setSuppressExceptions(Boolean.toString(suppressExceptions));
         expression.setAllowSimple(Boolean.toString(allowSimple));
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -438,7 +447,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setAllowSimple(Boolean.toString(allowSimple));
         expression.setResultType(resultType);
         expression.setHeaderName(headerName);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -584,7 +593,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T simple(String text, Class<?> resultType) {
         SimpleExpression expression = new SimpleExpression(text);
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -678,7 +687,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setToken(token);
         expression.setHeaderName(headerName);
         expression.setRegex(Boolean.toString(regex));
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -710,7 +719,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setHeaderName(headerName);
         expression.setRegex(Boolean.toString(regex));
         expression.setSkipFirst(Boolean.toString(skipFirst));
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -761,7 +770,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setGroup(group);
         expression.setGroupDelimiter(groupDelimiter);
         expression.setSkipFirst(Boolean.toString(skipFirst));
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -778,7 +787,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setToken(startToken);
         expression.setEndToken(endToken);
         expression.setIncludeTokens(Boolean.toString(includeTokens));
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -808,7 +817,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setInheritNamespaceTagName(inheritNamespaceTagName);
         expression.setXml(Boolean.toString(true));
         expression.setGroup(group);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -830,7 +839,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         if (group > 0) {
             expression.setGroup(Integer.toString(group));
         }
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -868,7 +877,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xpath(String text, Class<?> resultType) {
         XPathExpression expression = new XPathExpression(text);
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -884,7 +893,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xpath(String text, Class<?> resultType, String headerName) {
         XPathExpression expression = new XPathExpression(text);
         expression.setHeaderName(headerName);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -916,7 +925,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setResultType(resultType);
         expression.setNamespaces(namespaces.getNamespaces());
         expression.setHeaderName(headerName);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -933,7 +942,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         XPathExpression expression = new XPathExpression(text);
         expression.setResultType(resultType);
         expression.setNamespaces(namespaces);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -960,7 +969,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xpath(String text, Map<String, String> namespaces) {
         XPathExpression expression = new XPathExpression(text);
         expression.setNamespaces(namespaces);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -997,7 +1006,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xquery(String text, Class<?> resultType) {
         XQueryExpression expression = new XQueryExpression(text);
         expression.setResultType(resultType);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -1012,7 +1021,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xquery(String text, Class<?> resultType, String headerName) {
         XQueryExpression expression = new XQueryExpression(text);
         expression.setHeaderName(headerName);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -1044,7 +1053,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         expression.setResultType(resultType);
         expression.setNamespaces(namespaces.getNamespaces());
         expression.setHeaderName(headerName);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -1061,7 +1070,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         XQueryExpression expression = new XQueryExpression(text);
         expression.setResultType(resultType);
         expression.setNamespaces(namespaces);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -1088,7 +1097,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
     public T xquery(String text, Map<String, String> namespaces) {
         XQueryExpression expression = new XQueryExpression(text);
         expression.setNamespaces(namespaces);
-        setExpressionType(expression);
+        expression(expression);
         return result;
     }
 
@@ -1101,7 +1110,7 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
      */
     public T language(String language, String expression) {
         LanguageExpression exp = new LanguageExpression(language, expression);
-        setExpressionType(exp);
+        expression(exp);
         return result;
     }
 
@@ -1129,6 +1138,19 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware {
         return expressionType;
     }
 
+    public PredicateFactory getPredicateType() {
+        return predicateType;
+    }
+
+    public void setPredicateType(PredicateFactory predicateType) {
+        this.predicateType = predicateType;
+    }
+
+    @Override
+    public PredicateFactory getPredicateFactory() {
+        return predicateType;
+    }
+
     protected Expression createExpression(CamelContext camelContext) {
         if (getExpressionValue() == null) {
             if (getExpressionType() != null) {
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/ExpressionDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/ExpressionDefinition.java
index a675d2a..cc84a74 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/ExpressionDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/ExpressionDefinition.java
@@ -33,9 +33,11 @@ import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
 import org.apache.camel.ExpressionFactory;
 import org.apache.camel.Predicate;
+import org.apache.camel.PredicateFactory;
 import org.apache.camel.model.ModelCamelContext;
 import org.apache.camel.spi.ExpressionFactoryAware;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.PredicateFactoryAware;
 import org.apache.camel.util.ObjectHelper;
 
 /**
@@ -46,7 +48,8 @@ import org.apache.camel.util.ObjectHelper;
 @XmlType(name = "expression") // must be named expression
 @XmlAccessorType(XmlAccessType.FIELD)
 @SuppressWarnings("rawtypes")
-public class ExpressionDefinition implements Expression, Predicate, ExpressionFactory, ExpressionFactoryAware {
+public class ExpressionDefinition
+        implements Expression, Predicate, ExpressionFactory, ExpressionFactoryAware, PredicateFactory, PredicateFactoryAware {
     @XmlAttribute
     @XmlID
     private String id;
@@ -196,6 +199,11 @@ public class ExpressionDefinition implements Expression, Predicate, ExpressionFa
     }
 
     @Override
+    public PredicateFactory getPredicateFactory() {
+        return this;
+    }
+
+    @Override
     public Expression createExpression(CamelContext camelContext) {
         return camelContext.adapt(ModelCamelContext.class).createExpression(this);
     }
@@ -240,4 +248,10 @@ public class ExpressionDefinition implements Expression, Predicate, ExpressionFa
         }
     }
 
+    @Override
+    public void initPredicate(CamelContext context) {
+        if (predicate == null) {
+            predicate = createPredicate(context);
+        }
+    }
 }
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java
index f57e3b1..cf80e7e 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java
@@ -51,7 +51,7 @@ public class ChoiceProcessor extends AsyncProcessorSupport implements Navigate<P
 
     public ChoiceProcessor(List<FilterProcessor> filters, Processor otherwise) {
         this.filters = filters;
-        this.otherwise = AsyncProcessorConverterHelper.convert(otherwise);
+        this.otherwise = otherwise != null ? AsyncProcessorConverterHelper.convert(otherwise) : null;
     }
 
     @Override
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
index 4e50324..0fb98a5 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/ExpressionReifier.java
@@ -230,7 +230,9 @@ public class ExpressionReifier<T extends ExpressionDefinition> extends AbstractR
         if (predicate instanceof CamelContextAware) {
             ((CamelContextAware) predicate).setCamelContext(camelContext);
         }
-        predicate.init(camelContext);
+        // if the predicate is created via a delegate then it would need to know if its a predicate or expression
+        // when being initialized
+        predicate.initPredicate(camelContext);
         return predicate;
     }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ChoiceNoOtherwiseTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ChoiceNoOtherwiseTest.java
new file mode 100644
index 0000000..18e5b01
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ChoiceNoOtherwiseTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ChoiceNoOtherwiseTest extends ContextTestSupport {
+    protected MockEndpoint x;
+    protected MockEndpoint end;
+
+    @Test
+    public void testNoOtherwise() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").choice().when().simple("${header.foo} == 'bar'").to("mock:x").end().to("mock:end");
+            }
+        });
+        context.start();
+
+        x.expectedBodiesReceived("a");
+        end.expectedBodiesReceived("a", "b");
+
+        sendMessage("bar", "a");
+        sendMessage("cheese", "b");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testNoOtherwiseTwo() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").choice().when(simple("${header.foo} == 'bar'")).to("mock:x").end().to("mock:end");
+            }
+        });
+        context.start();
+
+        x.expectedBodiesReceived("a");
+        end.expectedBodiesReceived("a", "b");
+
+        sendMessage("bar", "a");
+        sendMessage("cheese", "b");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testEmptyOtherwise() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").choice().when().simple("${header.foo} == 'bar'").to("mock:x").otherwise().end().to("mock:end");
+            }
+        });
+        context.start();
+
+        x.expectedBodiesReceived("a");
+        end.expectedBodiesReceived("a", "b");
+
+        sendMessage("bar", "a");
+        sendMessage("cheese", "b");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testEmptyOtherwiseTwo() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start").choice().when(simple("${header.foo} == 'bar'")).to("mock:x").otherwise().end().to("mock:end");
+            }
+        });
+        context.start();
+
+        x.expectedBodiesReceived("a");
+        end.expectedBodiesReceived("a", "b");
+
+        sendMessage("bar", "a");
+        sendMessage("cheese", "b");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    protected void sendMessage(final Object headerValue, final Object body) throws Exception {
+        template.sendBodyAndHeader("direct:start", body, "foo", headerValue);
+    }
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        super.setUp();
+
+        x = getMockEndpoint("mock:x");
+        end = getMockEndpoint("mock:end");
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+}