CAMEL-11421: Up in the sky coding. Tokenize language can now set the group size using dynamic values based on simple language like some of the other options can do.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/2f8b0190 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/2f8b0190 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/2f8b0190 Branch: refs/heads/master Commit: 2f8b019062652a1f94728dac7ef9dc70d53c21fb Parents: 848512f Author: Claus Ibsen <[email protected]> Authored: Sun Jun 18 15:49:31 2017 +0200 Committer: Claus Ibsen <[email protected]> Committed: Mon Jun 19 08:34:17 2017 +0200 ---------------------------------------------------------------------- camel-core/src/main/docs/tokenize-language.adoc | 2 +- .../apache/camel/builder/ExpressionBuilder.java | 28 ++++++++-- .../apache/camel/builder/ExpressionClause.java | 25 +++++++++ .../camel/builder/ExpressionClauseSupport.java | 30 +++++++++- .../org/apache/camel/builder/ValueBuilder.java | 6 +- .../language/tokenizer/TokenizeLanguage.java | 10 ++-- .../model/language/TokenizerExpression.java | 10 ++-- .../SplitTokenizerGroupDynamicTest.java | 59 ++++++++++++++++++++ .../SpringSplitTokenizerGroupDynamicTest.java | 32 +++++++++++ .../SpringSplitTokenizerGroupDynamicTest.xml | 46 +++++++++++++++ 10 files changed, 226 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/docs/tokenize-language.adoc ---------------------------------------------------------------------- diff --git a/camel-core/src/main/docs/tokenize-language.adoc b/camel-core/src/main/docs/tokenize-language.adoc index 8d5daf0..25ade1c 100644 --- a/camel-core/src/main/docs/tokenize-language.adoc +++ b/camel-core/src/main/docs/tokenize-language.adoc @@ -30,7 +30,7 @@ The Tokenize language supports 10 options which are listed below. | regex | false | Boolean | If the token is a regular expression pattern. The default value is false | xml | false | Boolean | Whether the input is XML messages. This option must be set to true if working with XML payloads. | includeTokens | false | Boolean | Whether to include the tokens in the parts when using pairs The default value is false -| group | | Integer | To group N parts together for example to split big files into chunks of 1000 lines. +| group | | String | To group N parts together for example to split big files into chunks of 1000 lines. You can use simple language as the group to support dynamic group sizes. | skipFirst | false | Boolean | To skip the very first element | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks |======================================================================= http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java index 60b2300..6fdc509 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java @@ -40,12 +40,14 @@ import org.apache.camel.Component; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.Expression; +import org.apache.camel.ExpressionEvaluationException; import org.apache.camel.InvalidPayloadException; import org.apache.camel.Message; import org.apache.camel.NoSuchEndpointException; import org.apache.camel.NoSuchLanguageException; import org.apache.camel.NoTypeConversionAvailableException; import org.apache.camel.Producer; +import org.apache.camel.RuntimeExchangeException; import org.apache.camel.component.bean.BeanInvocation; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.language.bean.BeanLanguage; @@ -1639,14 +1641,21 @@ public final class ExpressionBuilder { }; } - public static Expression groupXmlIteratorExpression(final Expression expression, final int group) { + public static Expression groupXmlIteratorExpression(final Expression expression, final String group) { return new ExpressionAdapter() { public Object evaluate(Exchange exchange) { // evaluate expression as iterator Iterator<?> it = expression.evaluate(exchange, Iterator.class); ObjectHelper.notNull(it, "expression: " + expression + " evaluated on " + exchange + " must return an java.util.Iterator"); // must use GroupTokenIterator in xml mode as we want to concat the xml parts into a single message - return new GroupTokenIterator(exchange, it, null, group, false); + // the group can be a simple expression so evaluate it as a number + Integer parts = exchange.getContext().resolveLanguage("simple").createExpression(group).evaluate(exchange, Integer.class); + if (parts == null) { + throw new RuntimeExchangeException("Group evaluated as null, must be evaluated as a positive Integer value from expression: " + group, exchange); + } else if (parts <= 0) { + throw new RuntimeExchangeException("Group must be a positive number, was: " + parts, exchange); + } + return new GroupTokenIterator(exchange, it, null, parts, false); } @Override @@ -1656,16 +1665,23 @@ public final class ExpressionBuilder { }; } - public static Expression groupIteratorExpression(final Expression expression, final String token, final int group, final boolean skipFirst) { + public static Expression groupIteratorExpression(final Expression expression, final String token, final String group, final boolean skipFirst) { return new ExpressionAdapter() { public Object evaluate(Exchange exchange) { // evaluate expression as iterator Iterator<?> it = expression.evaluate(exchange, Iterator.class); ObjectHelper.notNull(it, "expression: " + expression + " evaluated on " + exchange + " must return an java.util.Iterator"); + // the group can be a simple expression so evaluate it as a number + Integer parts = exchange.getContext().resolveLanguage("simple").createExpression(group).evaluate(exchange, Integer.class); + if (parts == null) { + throw new RuntimeExchangeException("Group evaluated as null, must be evaluated as a positive Integer value from expression: " + group, exchange); + } else if (parts <= 0) { + throw new RuntimeExchangeException("Group must be a positive number, was: " + parts, exchange); + } if (token != null) { - return new GroupTokenIterator(exchange, it, token, group, skipFirst); + return new GroupTokenIterator(exchange, it, token, parts, skipFirst); } else { - return new GroupIterator(exchange, it, group, skipFirst); + return new GroupIterator(exchange, it, parts, skipFirst); } } @@ -2382,7 +2398,7 @@ public final class ExpressionBuilder { public Object evaluate(Exchange exchange) { // use simple language Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(expression); - return ExpressionBuilder.groupIteratorExpression(exp, null, group, false).evaluate(exchange, Object.class); + return ExpressionBuilder.groupIteratorExpression(exp, null, "" + group, false).evaluate(exchange, Object.class); } @Override http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java index 07e9c85..05fd859 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClause.java @@ -679,6 +679,18 @@ public class ExpressionClause<T> extends ExpressionDefinition { * @param token the token * @param regex whether the token is a regular expression or not * @param group to group by the given number + * @return the builder to continue processing the DSL + */ + public T tokenize(String token, boolean regex, String group) { + return tokenize(token, regex, group, false); + } + + /** + * Evaluates a token expression on the message body + * + * @param token the token + * @param regex whether the token is a regular expression or not + * @param group to group by the given number * @param skipFirst whether to skip the first element * @return the builder to continue processing the DSL */ @@ -690,6 +702,19 @@ public class ExpressionClause<T> extends ExpressionDefinition { * Evaluates a token expression on the message body * * @param token the token + * @param regex whether the token is a regular expression or not + * @param group to group by the given number + * @param skipFirst whether to skip the first element + * @return the builder to continue processing the DSL + */ + public T tokenize(String token, boolean regex, String group, boolean skipFirst) { + return delegate.tokenize(token, null, regex, group, skipFirst); + } + + /** + * Evaluates a token expression on the message body + * + * @param token the token * @param group to group by the given number * @return the builder to continue processing the DSL */ http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java index 910645f..64cef85 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java @@ -708,6 +708,20 @@ public class ExpressionClauseSupport<T> { * @return the builder to continue processing the DSL */ public T tokenize(String token, String headerName, boolean regex, int group, boolean skipFirst) { + return tokenize(token, headerName, regex, "" + group, skipFirst); + } + + /** + * Evaluates a token expression on the given header + * + * @param token the token + * @param headerName name of header to tokenize + * @param regex whether the token is a regular expression or not + * @param group to group by number of parts + * @param skipFirst whether to skip the very first element + * @return the builder to continue processing the DSL + */ + public T tokenize(String token, String headerName, boolean regex, String group, boolean skipFirst) { TokenizerExpression expression = new TokenizerExpression(); expression.setToken(token); expression.setHeaderName(headerName); @@ -744,13 +758,23 @@ public class ExpressionClauseSupport<T> { * @return the builder to continue processing the DSL */ public T tokenizeXMLPair(String tagName, String inheritNamespaceTagName, int group) { + return tokenizeXMLPair(tagName, inheritNamespaceTagName, "" + group); + } + + /** + * Evaluates a token pair expression on the message body with XML content + * + * @param tagName the the tag name of the child nodes to tokenize + * @param inheritNamespaceTagName optional parent or root tag name that contains namespace(s) to inherit + * @param group to group by the given number + * @return the builder to continue processing the DSL + */ + public T tokenizeXMLPair(String tagName, String inheritNamespaceTagName, String group) { TokenizerExpression expression = new TokenizerExpression(); expression.setToken(tagName); expression.setInheritNamespaceTagName(inheritNamespaceTagName); expression.setXml(true); - if (group > 0) { - expression.setGroup(group); - } + expression.setGroup(group); setExpressionType(expression); return result; } http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/builder/ValueBuilder.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/builder/ValueBuilder.java b/camel-core/src/main/java/org/apache/camel/builder/ValueBuilder.java index 91b5fcd..eb87b11 100644 --- a/camel-core/src/main/java/org/apache/camel/builder/ValueBuilder.java +++ b/camel-core/src/main/java/org/apache/camel/builder/ValueBuilder.java @@ -185,8 +185,12 @@ public class ValueBuilder implements Expression, Predicate { } public ValueBuilder tokenize(String token, int group, boolean skipFirst) { + return tokenize(token, "" + group, skipFirst); + } + + public ValueBuilder tokenize(String token, String group, boolean skipFirst) { Expression newExp = ExpressionBuilder.tokenizeExpression(expression, token); - if (group == 0 && skipFirst) { + if (group == null && skipFirst) { // wrap in skip first (if group then it has its own skip first logic) newExp = ExpressionBuilder.skipFirstExpression(newExp); } http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java b/camel-core/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java index 22b62a2..33bf48d 100644 --- a/camel-core/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java +++ b/camel-core/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java @@ -46,7 +46,7 @@ public class TokenizeLanguage implements Language, IsSingleton { private boolean regex; private boolean xml; private boolean includeTokens; - private int group; + private String group; private boolean skipFirst; public static Expression tokenize(String token) { @@ -121,14 +121,14 @@ public class TokenizeLanguage implements Language, IsSingleton { } else { answer = ExpressionBuilder.tokenizeExpression(exp, token); } - if (group == 0 && skipFirst) { + if (group == null && skipFirst) { // wrap in skip first (if group then it has its own skip first logic) answer = ExpressionBuilder.skipFirstExpression(answer); } } // if group then wrap answer in group expression - if (group > 0) { + if (group != null) { if (isXml()) { answer = ExpressionBuilder.groupXmlIteratorExpression(answer, group); } else { @@ -202,11 +202,11 @@ public class TokenizeLanguage implements Language, IsSingleton { this.includeTokens = includeTokens; } - public int getGroup() { + public String getGroup() { return group; } - public void setGroup(int group) { + public void setGroup(String group) { this.group = group; } http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/main/java/org/apache/camel/model/language/TokenizerExpression.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/language/TokenizerExpression.java b/camel-core/src/main/java/org/apache/camel/model/language/TokenizerExpression.java index 83aacd6..2099317 100644 --- a/camel-core/src/main/java/org/apache/camel/model/language/TokenizerExpression.java +++ b/camel-core/src/main/java/org/apache/camel/model/language/TokenizerExpression.java @@ -52,7 +52,7 @@ public class TokenizerExpression extends ExpressionDefinition { @XmlAttribute private Boolean includeTokens; @XmlAttribute - private Integer group; + private String group; @XmlAttribute private Boolean skipFirst; @@ -149,14 +149,15 @@ public class TokenizerExpression extends ExpressionDefinition { this.includeTokens = includeTokens; } - public Integer getGroup() { + public String getGroup() { return group; } /** * To group N parts together, for example to split big files into chunks of 1000 lines. + * You can use simple language as the group to support dynamic group sizes. */ - public void setGroup(Integer group) { + public void setGroup(String group) { this.group = group; } @@ -193,9 +194,6 @@ public class TokenizerExpression extends ExpressionDefinition { language.setIncludeTokens(includeTokens); } if (group != null) { - if (group <= 0) { - throw new IllegalArgumentException("Group must be a positive number, was: " + group); - } language.setGroup(group); } if (skipFirst != null) { http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/test/java/org/apache/camel/processor/SplitTokenizerGroupDynamicTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/processor/SplitTokenizerGroupDynamicTest.java b/camel-core/src/test/java/org/apache/camel/processor/SplitTokenizerGroupDynamicTest.java new file mode 100644 index 0000000..53436a6 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/processor/SplitTokenizerGroupDynamicTest.java @@ -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. + */ +package org.apache.camel.processor; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; + +public class SplitTokenizerGroupDynamicTest extends ContextTestSupport { + + public void testSplitTokenizerA() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:split"); + mock.expectedBodiesReceived("Claus,James", "Willem"); + + template.sendBodyAndHeader("direct:a", "Claus,James,Willem", "groups", 2); + + assertMockEndpointsSatisfied(); + } + + public void testSplitTokenizerB() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:split"); + mock.expectedBodiesReceived("James,Willem"); + + template.sendBodyAndHeader("direct:b", "Claus,James,Willem".getBytes(), "groups", 2); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + + from("direct:a") + .split().tokenize(",", false, "${header.groups}") + .to("mock:split"); + + from("direct:b") + .split(bodyAs(String.class).tokenize(",", "${header.groups}", true)) + .to("mock:split"); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/test/java/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.java b/camel-core/src/test/java/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.java new file mode 100644 index 0000000..d5cd66b --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.java @@ -0,0 +1,32 @@ +/** + * 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.spring.processor; + +import org.apache.camel.CamelContext; +import org.apache.camel.processor.SplitTokenizerGroupDynamicTest; + +import static org.apache.camel.spring.processor.SpringTestHelper.createSpringCamelContext; + +/** + * @version + */ +public class SpringSplitTokenizerGroupDynamicTest extends SplitTokenizerGroupDynamicTest { + + protected CamelContext createCamelContext() throws Exception { + return createSpringCamelContext(this, "org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.xml"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/2f8b0190/camel-core/src/test/resources/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.xml ---------------------------------------------------------------------- diff --git a/camel-core/src/test/resources/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.xml b/camel-core/src/test/resources/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.xml new file mode 100644 index 0000000..7744a14 --- /dev/null +++ b/camel-core/src/test/resources/org/apache/camel/spring/processor/SpringSplitTokenizerGroupDynamicTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd + "> + + <!-- START SNIPPET: example --> + <camelContext xmlns="http://camel.apache.org/schema/spring"> + <route> + <from uri="direct:a"/> + <split> + <tokenize token="," group="${header.groups}"/> + <to uri="mock:split"/> + </split> + </route> + <route> + <from uri="direct:b"/> + <split> + <tokenize token="," group="${header.groups}" skipFirst="true"/> + <to uri="mock:split"/> + </split> + </route> + </camelContext> + <!-- END SNIPPET: example --> + +</beans>
