Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ private HeaderChecks() {
*/
static final Map<String, String> METADATA_OPTION = Map.of("timer", "includeMetadata");

/**
* A comparison of the timer's counter with a number: the counter counts the fires from 1, so {@code == 0} is never
* true (CAMEL-25744: in 8 of 10 benchmark runs a model compared it with 0 to pick the first fire). The Simple forms
* (${exchangeProperty.CamelTimerCounter} == 0, ${header.CamelTimerCounter == 0 ? ...}) and the Groovy ones
* (exchange.getProperty('CamelTimerCounter', Integer) == 0).
*/
static final Pattern TIMER_COUNTER_COMPARISON = Pattern.compile(
"CamelTimerCounter(?:\\}|['\"]\\s*(?:,\\s*[\\w.]+\\s*)?\\))?\\s*(==|!=|<=|<|>=|>)\\s*['\"]?(\\d+)['\"]?");

Comment thread
davsclaus marked this conversation as resolved.
Resolved
static final Pattern EXCHANGE_PROPERTY_REF_PATTERN = Pattern.compile("\\$\\{exchangeProperty\\.(Camel[A-Z][A-Za-z0-9]*)");

/** Whether the option that makes the component set its exchange properties is true somewhere in the content. */
Expand Down Expand Up @@ -106,11 +115,36 @@ static String intentHint(String name) {
return null;
}

/**
* The comparisons of CamelTimerCounter that can never be true (== 0, <= 0, < 1) or never be false (!= 0, > 0, >=
* 1), as the counter is 1 on the first fire.
*/
static List<String> timerCounterComparisons(String content) {
List<String> msgs = new ArrayList<>();
String[] lines = content.split("\n", -1);
for (int i = 0; i < lines.length; i++) {
Matcher m = TIMER_COUNTER_COMPARISON.matcher(lines[i]);
while (m.find()) {
String op = m.group(1);
int n = Integer.parseInt(m.group(2));
boolean never = ("==".equals(op) || "<=".equals(op)) && n == 0 || "<".equals(op) && n <= 1;
boolean always = ("!=".equals(op) || ">".equals(op)) && n == 0 || ">=".equals(op) && n <= 1;
if (never || always) {
msgs.add("Line " + (i + 1) + ": CamelTimerCounter counts the fires from 1 (the first fire is 1), so "
+ op + " " + n + " is " + (never ? "never true" : "always true")
+ ": compare with 1 to pick the first fire");
}
}
}
return msgs;
}

public static List<String> validateKnownHeaders(String content, CamelCatalog catalog) {
List<String> msgs = new ArrayList<>();
if (content == null) {
return msgs;
}
msgs.addAll(timerCounterComparisons(content));
Set<String> known = new LinkedHashSet<>();
Map<String, String> owner = new LinkedHashMap<>();
Matcher sm = SCHEME_IN_URI_PATTERN.matcher(content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,65 @@ void aTimerExchangePropertyIsSetOnlyWithIncludeMetadata() {
assertThat(msgs.get(1)).startsWith("Line 4:").contains("timer sets CamelTimerName only with includeMetadata=true");
}

/** CAMEL-25744: the counter is 1 on the first fire, so a comparison with 0 never picks it. */
@Test
void aTimerCounterComparisonThatCanNeverBeTrueIsReported() {
List<String> msgs = SourceValidator.validateKnownHeaders("""
- from:
uri: "timer:tick?repeatCount=2&includeMetadata=true"
steps:
- setBody:
simple: "resource:file:${exchangeProperty.CamelTimerCounter == 0 ? 'a.json' : 'b.json'}"
- choice:
when:
- simple: "${header.CamelTimerCounter} != 0"
steps:
- log: "${body}"
- filter:
groovy: "exchange.getProperty('CamelTimerCounter', Integer) < 1"
""", catalog);
assertThat(msgs).hasSize(3);
assertThat(msgs.get(0)).startsWith("Line 5:").contains("counts the fires from 1").contains("== 0 is never true")
.contains("compare with 1");
assertThat(msgs.get(1)).startsWith("Line 8:").contains("!= 0 is always true");
assertThat(msgs.get(2)).startsWith("Line 12:").contains("< 1 is never true");
}

@Test
void aTimerCounterComparisonThatIsAlwaysTrueIsReported() {
List<String> msgs = SourceValidator.validateKnownHeaders("""
- from:
uri: "timer:tick?repeatCount=2&includeMetadata=true"
steps:
- filter:
simple: "${exchangeProperty.CamelTimerCounter} > 0"
- filter:
groovy: "exchange.getProperty('CamelTimerCounter', Integer) >= 1"
- filter:
simple: "${exchangeProperty.CamelTimerCounter} >= 0"
""", catalog);
assertThat(msgs).hasSize(3);
assertThat(msgs.get(0)).startsWith("Line 5:").contains("> 0 is always true");
assertThat(msgs.get(1)).startsWith("Line 7:").contains(">= 1 is always true");
assertThat(msgs.get(2)).startsWith("Line 9:").contains(">= 0 is always true");
}

@Test
void aTimerCounterComparedWithOneIsFine() {
List<String> msgs = SourceValidator.validateKnownHeaders("""
- from:
uri: "timer:tick?repeatCount=2&includeMetadata=true"
steps:
- setBody:
simple: "resource:file:${exchangeProperty.CamelTimerCounter == 1 ? 'a.json' : 'b.json'}"
- filter:
simple: "${exchangeProperty.CamelTimerCounter} > 1"
- filter:
simple: "${exchangeProperty.CamelTimerCounter} >= 2"
""", catalog);
assertThat(msgs).isEmpty();
}

@Test
void withIncludeMetadataAHeaderLookupFindsTheExchangeProperty() {
// Simple's ${header.x} falls back to the exchange properties, so it reads the counter
Expand Down
Loading