From bf071376b576cb9a1d294822ba721b324070480e Mon Sep 17 00:00:00 2001 From: Gary Tully Date: Fri, 3 May 2013 20:43:59 +0000 Subject: [PATCH] https://issues.apache.org/jira/browse/AMQ-3097 - apply patch with some additional unit test, with thanks git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1478973 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/grammar/SelectorParser.jj | 41 ++- .../filter/BooleanFunctionCallExpr.java | 66 ++++ .../filter/FunctionCallExpression.java | 281 ++++++++++++++++++ .../function/BuiltinFunctionRegistry.java | 37 +++ .../filter/function/FilterFunction.java | 61 ++++ .../filter/function/inListFunction.java | 93 ++++++ .../filter/function/makeListFunction.java | 82 +++++ .../activemq/filter/function/package.html | 27 ++ .../filter/function/regexMatchFunction.java | 166 +++++++++++ .../filter/function/replaceFunction.java | 84 ++++++ .../filter/function/splitFunction.java | 87 ++++++ .../activemq/selector/SelectorParserTest.java | 15 + .../activemq/selector/SelectorTest.java | 18 ++ 13 files changed, 1057 insertions(+), 1 deletion(-) create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/BooleanFunctionCallExpr.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/FunctionCallExpression.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/BuiltinFunctionRegistry.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/FilterFunction.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/inListFunction.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/makeListFunction.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/package.html create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/regexMatchFunction.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/replaceFunction.java create mode 100644 activemq-client/src/main/java/org/apache/activemq/filter/function/splitFunction.java diff --git a/activemq-client/src/main/grammar/SelectorParser.jj b/activemq-client/src/main/grammar/SelectorParser.jj index 2d4f98a728..91a46fbe5a 100755 --- a/activemq-client/src/main/grammar/SelectorParser.jj +++ b/activemq-client/src/main/grammar/SelectorParser.jj @@ -56,6 +56,7 @@ import java.util.*; import javax.jms.InvalidSelectorException; import org.apache.activemq.filter.*; +import org.apache.activemq.filter.FunctionCallExpression.invalidFunctionExpressionException; import org.apache.activemq.util.LRUCache; /** @@ -129,7 +130,6 @@ public class SelectorParser { } throw new ParseException("Expression will not result in a boolean value: " + value); } - } @@ -490,6 +490,9 @@ Expression unaryExpr() : left = UnaryExpression.createXQuery( s ); } | + LOOKAHEAD( "(" ) + left = functionCallExpr() + | left = primaryExpr() ) { @@ -498,6 +501,42 @@ Expression unaryExpr() : } +Expression functionCallExpr () : +{ + Token func_name; + FunctionCallExpression func_call = null; + Expression arg = null; + ArrayList arg_list = new ArrayList(); +} +{ + func_name = "(" + ( + arg = unaryExpr() + { + arg_list.add(arg); + } + ( + "," + arg = unaryExpr() + { + arg_list.add(arg); + } + ) * + ) + ")" + { + try + { + return FunctionCallExpression.createFunctionCall(func_name.image, arg_list); + } + catch ( invalidFunctionExpressionException inv_exc ) + { + // Re-throw as an error to avoid the need to propogate the throws declaration. + throw new Error("invalid function call expression", inv_exc); + } + } +} + Expression primaryExpr() : { Expression left=null; diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/BooleanFunctionCallExpr.java b/activemq-client/src/main/java/org/apache/activemq/filter/BooleanFunctionCallExpr.java new file mode 100644 index 0000000000..dcd310228a --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/BooleanFunctionCallExpr.java @@ -0,0 +1,66 @@ +/** + * 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.activemq.filter; + +import java.util.List; + +/** + * Function call expression that evaluates to a boolean value. Selector parsing requires BooleanExpression objects for + * Boolean expressions, such as operands to AND, and as the final result of a selector. This provides that interface + * for function call expressions that resolve to Boolean values. + *

+ * If a function can return different types at evaluation-time, the function implementation needs to decide whether it + * supports casting to Boolean at parse-time. + * + * @see FunctionCallExpression#createFunctionCall + */ + +public class BooleanFunctionCallExpr extends FunctionCallExpression implements BooleanExpression { + /** + * Constructs a function call expression with the named filter function and arguments, which returns a boolean + * result. + * + * @param func_name - Name of the filter function to be called when evaluated. + * @param args - List of argument expressions passed to the function. + */ + + public BooleanFunctionCallExpr(String func_name, List args) + throws invalidFunctionExpressionException { + super(func_name, args); + } + + + /** + * Evaluate the function call expression, in the given context, and return an indication of whether the + * expression "matches" (i.e. evaluates to true). + * + * @param message_ctx - message context against which the expression will be evaluated. + * @return the boolean evaluation of the function call expression. + */ + + public boolean matches(MessageEvaluationContext message_ctx) throws javax.jms.JMSException { + Boolean result; + + result = (Boolean) evaluate(message_ctx); + + if (result != null) + return result.booleanValue(); + + return false; + } +} + diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/FunctionCallExpression.java b/activemq-client/src/main/java/org/apache/activemq/filter/FunctionCallExpression.java new file mode 100644 index 0000000000..ec4590fcd1 --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/FunctionCallExpression.java @@ -0,0 +1,281 @@ +/** + * 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.activemq.filter; + +import java.util.HashMap; +import java.util.List; +import org.apache.activemq.filter.function.FilterFunction; + +/** + * Function call expression for use in selector expressions. Includes an extensible interface to allow custom + * functions to be added without changes to the core. + *

+ * Use registerFunction() to register new function implementations for use in selectors. + */ + +public class FunctionCallExpression implements Expression { + protected static final HashMap functionRegistry = new HashMap(); + + protected String functionName; + protected java.util.ArrayList arguments; + protected FilterFunction filterFunc; + + static { + // Register the built-in functions. It would be nice to just have each function class register + // itself, but that only works when the classes are loaded, which may be never. + + org.apache.activemq.filter.function.BuiltinFunctionRegistry.register(); + } + + + /** + * Register the function with the specified name. + * + * @param name - the function name, as used in selector expressions. Case Sensitive. + * @param impl - class which implements the function interface, including parse-time and evaluation-time + * operations. + * @return true - if the function is successfully registered; false - if a function with the same name is + * already registered. + */ + + public static boolean registerFunction(String name, FilterFunction impl) { + boolean result; + + result = true; + + synchronized (functionRegistry) { + if (functionRegistry.containsKey(name)) + result = false; + else + functionRegistry.put(name, new functionRegistration(impl)); + } + + return result; + } + + /** + * Remove the registration of the function with the specified name. + *

+ * Note that parsed expressions using this function will still access its implementation after this call. + * + * @param name - name of the function to remove. + */ + + public static void deregisterFunction(String name) { + synchronized (functionRegistry) { + functionRegistry.remove(name); + } + } + + + /** + * Constructs a function call expression with the named function and argument list. + *

+ * Use createFunctionCall() to create instances. + * + * @exception invalidFunctionExpressionException - if the function name is not valid. + */ + + protected FunctionCallExpression(String func_name, List args) + throws invalidFunctionExpressionException { + functionRegistration func_reg; + + synchronized (functionRegistry) { + func_reg = functionRegistry.get(func_name); + } + + if (func_reg != null) { + this.arguments = new java.util.ArrayList(); + this.arguments.addAll(args); + this.functionName = func_name; + this.filterFunc = func_reg.getFilterFunction(); + } else { + throw new invalidFunctionExpressionException("invalid function name, \"" + func_name + "\""); + } + } + + + /** + * Create a function call expression for the named function and argument list, returning a Boolean function + * call expression if the function returns a boolean value so that it may be used in boolean contexts. + * Used by the parser when a function call is identified. Note that the function call is created after all + * argument expressions so that the function call can properly detect whether it evaluates to a Boolean value. + * + * @param func_name - name of the function, as used in selectors. + * @param args - list of argument expressions passed to the function. + * @return an instance of a BooleanFunctionCallExpr if the function returns a boolean value in this call, + * or a FunctionCallExpression otherwise. + * @exception invalidFunctionExpression - if the function name is not valid, or the given argument list is + * not valid for the function. + */ + + public static FunctionCallExpression createFunctionCall(String func_name, List args) + throws invalidFunctionExpressionException { + FunctionCallExpression result; + + // + // Create a function call expression by default to use with validating the function call + // expression and checking whether it returns a boolean result. + // + + result = new FunctionCallExpression(func_name, args); + + + // + // Check wether the function accepts this expression. I.E. are the arguments valid? + // + + if (result.filterFunc.isValid(result)) { + // + // If the result of the call is known to alwyas return a boolean value, wrap this + // expression as a valid BooleanExpression so it will be accepted as a boolean result + // by the selector grammar. + // + + if (result.filterFunc.returnsBoolean(result)) + result = new BooleanFunctionCallExpr(func_name, args); + } else { + // + // Function does not like this expression. + // + + throw new invalidFunctionExpressionException("invalid call of function " + func_name); + } + + return result; + } + + + /** + * Retrieve the number of arguments for the function call defined in this expression. + * + * @return the number of arguments being passed to the function. + */ + + public int getNumArguments() { + return arguments.size(); + } + + + /** + * Retrieve the argument at the specified index; the first argument is index 0. Used by implementations of + * FilterFunction objects to check arguments and evaluate them, as needed. + * + * @param which - number of the argument to retrieve; the first is 0. + */ + + public Expression getArgument(int which) { + return (Expression) arguments.get(which); + } + + + /** + * Evaluate the function call expression in the context given. + * + * @see Expression#evaluate + */ + + public Object evaluate(MessageEvaluationContext message_ctx) + throws javax.jms.JMSException { + return this.filterFunc.evaluate(this, message_ctx); + } + + + /** + * Translate the expression back into text in a form similar to the input to the selector parser. + */ + + @Override + public String toString() { + StringBuilder result; + boolean first_f; + + result = new StringBuilder(); + + result.append(functionName); + result.append("("); + first_f = true; + + for (Object arg : arguments) { + if (first_f) + first_f = false; + else + result.append(", "); + + result.append(arg.toString()); + } + + result.append(")"); + + return result.toString(); + } + + + //// //// + //// FUNCTION REGISTRATION //// + //// //// + + /** + * Maintain a single function registration. + */ + + protected static class functionRegistration { + protected FilterFunction filterFunction; + + /** + * Constructs a function registration for the given function implementation. + */ + + public functionRegistration(FilterFunction func) { + this.filterFunction = func; + } + + + /** + * Retrieve the filter function implementation. + */ + + public FilterFunction getFilterFunction() { + return filterFunction; + } + + + /** + * Set the filter function implementation for this registration. + */ + + public void setFilterFunction(FilterFunction func) { + filterFunction = func; + } + } + + + /** + * Exception indicating that an invalid function call expression was created, usually by the selector parser. + * Conditions include invalid function names and invalid function arguments. + */ + + public static class invalidFunctionExpressionException extends java.lang.Exception { + public invalidFunctionExpressionException(String msg) { + super(msg); + } + + public invalidFunctionExpressionException(String msg, java.lang.Throwable cause) { + super(msg, cause); + } + } +} diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/BuiltinFunctionRegistry.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/BuiltinFunctionRegistry.java new file mode 100644 index 0000000000..f114ca3b77 --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/BuiltinFunctionRegistry.java @@ -0,0 +1,37 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; + +/** + * Registry of built-in functions. Add built-in functions to this list to make sure they are registered at startup. + *

+ * Custom add-ons that are not built-in to the core ActiveMQ should not be listed here. + * Use FunctionCallExpression.registerFunction() directly. + */ + +public class BuiltinFunctionRegistry { + public static void register() { + FunctionCallExpression.registerFunction("INLIST", new inListFunction()); + FunctionCallExpression.registerFunction("MAKELIST", new makeListFunction()); + FunctionCallExpression.registerFunction("REGEX", new regexMatchFunction()); + FunctionCallExpression.registerFunction("REPLACE", new replaceFunction()); + FunctionCallExpression.registerFunction("SPLIT", new splitFunction()); + } +} + diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/FilterFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/FilterFunction.java new file mode 100644 index 0000000000..fae0e3777b --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/FilterFunction.java @@ -0,0 +1,61 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; + +/** + * Interface required for objects that will be registered as functions for use in selectors. Handles parse- + * time and evaluation-time operations. + */ + +public interface FilterFunction { + /** + * Check whether the function, as it is used, is valid. Checking arguments here will return errors + * to clients at the time invalid selectors are initially specified, rather than waiting until the selector is + * applied to a message. + * + * @param FunctionCallExpression expr - the full expression of the function call, as used. + * @return true - if the function call is valid; false - otherwise. + */ + public boolean isValid(FunctionCallExpression expr); + + /** + * Determine whether the function, as it will be called, returns a boolean value. Called during + * expression parsing after the full expression for the function call, including its arguments, has + * been parsed. This allows functions with variable return types to function as boolean expressions in + * selectors without sacrificing parse-time checking. + * + * @param FunctionCallExpression expr - the full expression of the function call, as used. + * @return true - if the function returns a boolean value for its use in the given expression; + * false - otherwise. + */ + public boolean returnsBoolean(FunctionCallExpression expr); + + + /** + * Evaluate the function call in the given context. The arguments must be evaluated, as-needed, by the + * function. Note that boolean expressions must return Boolean objects. + * + * @param FunctionCallExpression expr - the full expression of the function call, as used. + * @param MessageEvaluationContext message - the context within which to evaluate the call. + */ + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message) + throws javax.jms.JMSException; +} + diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/inListFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/inListFunction.java new file mode 100644 index 0000000000..7fa38abd3e --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/inListFunction.java @@ -0,0 +1,93 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; + +/** + * Filter function that matches a value against a list of values and evaluates to an indicator of membership in the + * list. For example: + *

+ *

+ * INLIST( SPLIT('1,2,3', ',') , '2' ) + *

+ *

+ * Note that the first argument must be a List. Strings containing lists are not acceptable; for example, + * INLIST('1,2,3', '1'), will cause an exception to be thrown at evaluation-time. + */ + +public class inListFunction implements FilterFunction { + /** + * Check whether the given expression is a valid call of this function. Two arguments are required. Note that + * the evaluated results of the arguments will be compared with Object#equals(). + * + * @param expr - the expression consisting of a call to this function. + * @return true - if the expression is valid; false - otherwise. + */ + + public boolean isValid(FunctionCallExpression expr) { + if (expr.getNumArguments() != 2) + return false; + + return true; + } + + + /** + * Check whether the given expression, which consists of a call to this function, evaluates to a Boolean. + * If the function can return different more than one type of value at evaluation-time, it must decide whether + * to cast the result to a Boolean at this time. + * + * @param expr - the expression consisting of a call to this function. + * @return true - if the expression is valid; false - otherwise. + */ + + public boolean returnsBoolean(FunctionCallExpression expr) { + return true; + } + + + /** + * Evalutate the given expression, which consists of a call to this function, in the context given. Checks + * whether the second argument is a member of the list in the first argument. + * + * @param expr - the expression consisting of a call to this function. + * @param message_ctx - the context in which the call is being evaluated. + * @return Boolean - the result of the evaluation. + */ + + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message_ctx) + throws javax.jms.JMSException { + java.util.List arr; + int cur; + Object cand; + boolean found_f; + + arr = (java.util.List) expr.getArgument(0).evaluate(message_ctx); + cand = expr.getArgument(1).evaluate(message_ctx); + + cur = 0; + found_f = false; + while ((cur < arr.size()) && (!found_f)) { + found_f = arr.get(cur).equals(cand); + cur++; + } + + return Boolean.valueOf(found_f); + } +} diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/makeListFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/makeListFunction.java new file mode 100644 index 0000000000..7fedd7bdab --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/makeListFunction.java @@ -0,0 +1,82 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; + +/** + * Filter function that creates a list with each argument being one element in the list. + * For example: + *

+ *

+ * MAKELIST( '1', '2', '3' ) + *

+ */ + +public class makeListFunction implements FilterFunction { + /** + * Check whether the given expression is a valid call of this function. Any number of arguments is accepted. + * + * @param expr - the expression consisting of a call to this function. + * @return true - if the expression is valid; false - otherwise. + */ + + public boolean isValid(FunctionCallExpression expr) { + return true; + } + + + /** + * Indicate that this function never evaluates to a Boolean result. + * + * @param expr - the expression consisting of a call to this function. + * @return false - this Filter Function never evaluates to a Boolean. + */ + + public boolean returnsBoolean(FunctionCallExpression expr) { + return false; + } + + + /** + * Evalutate the given expression, which consists of a call to this function, in the context given. Creates + * a list containing the evaluated results of its argument expressions. + * + * @param expr - the expression consisting of a call to this function. + * @param message_ctx - the context in which the call is being evaluated. + * @return java.util.List - the result of the evaluation. + */ + + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message) + throws javax.jms.JMSException { + java.util.ArrayList ele_arr; + int num_arg; + int cur; + + num_arg = expr.getNumArguments(); + ele_arr = new java.util.ArrayList(num_arg); + + cur = 0; + while (cur < num_arg) { + ele_arr.add(expr.getArgument(cur).evaluate(message)); + cur++; + } + + return (java.util.List) ele_arr; + } +} diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/package.html b/activemq-client/src/main/java/org/apache/activemq/filter/function/package.html new file mode 100644 index 0000000000..3fa6fdec71 --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/package.html @@ -0,0 +1,27 @@ + + + + + + +

+ Filter Function implementations for JMS Selectors. +

+ + + diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/regexMatchFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/regexMatchFunction.java new file mode 100644 index 0000000000..c9b32c9fdb --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/regexMatchFunction.java @@ -0,0 +1,166 @@ +/** + * 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.activemq.filter.function; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; +import org.apache.activemq.util.LRUCache; + +/** + * Filter function that matches a value against a regular expression. + *

+ *

+ * REGEX( 'A.B', 'A-B' ) + *

+ *

+ * Note that the regular expression is not anchored; use the anchor characters, ^ and $, as-needed. For example, + * REGEX( 'AA', 'XAAX' ) evaluates to true while REGEX( '^AA$' , 'XAAX' ) evaluates to false. + */ + +public class regexMatchFunction implements FilterFunction { + protected static final LRUCache compiledExprCache = new LRUCache(100); + + /** + * Check whether the given expression is a valid call of this function. Two arguments are required. When + * evaluated, the arguments are converted to strings if they are not already strings. + * + * @param expr - the expression consisting of a call to this function. + * @return true - if the expression is valid; false - otherwise. + */ + + public boolean isValid(FunctionCallExpression expr) { + if (expr.getNumArguments() == 2) + return true; + + return false; + } + + + /** + * Indicate that this Filter Function evaluates to a Boolean result. + * + * @param expr - the expression consisting of a call to this function. + * @return true - this function always evaluates to a Boolean result. + */ + + public boolean returnsBoolean(FunctionCallExpression expr) { + return true; + } + + /** + * Evalutate the given expression, which consists of a call to this function, in the context given. Returns + * an indication of whether the second argument matches the regular expression in the first argument. + * + * @param expr - the expression consisting of a call to this function. + * @param message_ctx - the context in which the call is being evaluated. + * @return true - if the value matches the regular expression; false - otherwise. + */ + + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message) + throws javax.jms.JMSException { + Object reg; + Object cand; + String reg_str; + String cand_str; + Pattern pat; + Matcher match_eng; + + // + // Evaluate the first argument (the regular expression). + // + reg = expr.getArgument(0).evaluate(message); + + if (reg != null) { + // Convert to a string, if it's not already a string. + if (reg instanceof String) + reg_str = (String) reg; + else + reg_str = reg.toString(); + + + // + // Evaluate the second argument (the candidate to match against the regular + // expression). + // + cand = expr.getArgument(1).evaluate(message); + + if (cand != null) { + // Convert to a string, if it's not already a string. + if (cand instanceof String) + cand_str = (String) cand; + else + cand_str = cand.toString(); + + + // + // Obtain the compiled regular expression and match it. + // + + pat = getCompiledPattern(reg_str); + match_eng = pat.matcher(cand_str); + + + // + // Return an indication of whether the regular expression matches at any + // point in the candidate (see Matcher#find()). + // + + return Boolean.valueOf(match_eng.find()); + } + } + + return Boolean.FALSE; + } + + + /** + * Retrieve a compiled pattern for the given pattern string. A cache of recently used strings is maintained to + * improve performance. + * + * @param reg_ex_str - the string specifying the regular expression. + * @return Pattern - compiled form of the regular expression. + */ + + protected Pattern getCompiledPattern(String reg_ex_str) { + Pattern result; + + // + // Look for the compiled pattern in the cache. + // + + synchronized (compiledExprCache) { + result = compiledExprCache.get(reg_ex_str); + } + + + // + // If it was not found, compile it and add it to the cache. + // + + if (result == null) { + result = Pattern.compile(reg_ex_str); + + synchronized (compiledExprCache) { + compiledExprCache.put(reg_ex_str, result); + } + } + + return result; + } +} diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/replaceFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/replaceFunction.java new file mode 100644 index 0000000000..503c8b60db --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/replaceFunction.java @@ -0,0 +1,84 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; + +/** + * Function which replaces regular expression matches in a source string to a replacement literal. + *

+ * For Example: + * REPLACE('1,2/3', '[,/]', ';') returns '1;2;3' + */ + +public class replaceFunction implements FilterFunction { + /** + * Check whether the given expression is valid for this function. + * + * @param expr - the expression consisting of a call to this function. + * @return true - if three arguments are passed to the function; false - otherwise. + */ + + public boolean isValid(FunctionCallExpression expr) { + if (expr.getNumArguments() == 3) + return true; + + return false; + } + + + /** + * Indicate that this function does not return a boolean value. + * + * @param expr - the expression consisting of a call to this function. + * @return false - this filter function always evaluates to a string. + */ + + public boolean returnsBoolean(FunctionCallExpression expr) { + return false; + } + + + /** + * Evaluate the given expression for this function in the given context. The result of the evaluation is a + * string with all matches of the regular expression, from the evaluation of the second argument, replaced by + * the string result from the evaluation of the third argument. Replacement is performed by + * String#replaceAll(). + *

+ * Note that all three arguments must be Strings. + * + * @param expr - the expression consisting of a call to this function. + * @return String - the result of the replacement. + */ + + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message_ctx) + throws javax.jms.JMSException { + String src; + String match_regex; + String repl_lit; + String result; + + src = (String) expr.getArgument(0).evaluate(message_ctx); + match_regex = (String) expr.getArgument(1).evaluate(message_ctx); + repl_lit = (String) expr.getArgument(2).evaluate(message_ctx); + + result = src.replaceAll(match_regex, repl_lit); + + return result; + } +} diff --git a/activemq-client/src/main/java/org/apache/activemq/filter/function/splitFunction.java b/activemq-client/src/main/java/org/apache/activemq/filter/function/splitFunction.java new file mode 100644 index 0000000000..c87105ed1c --- /dev/null +++ b/activemq-client/src/main/java/org/apache/activemq/filter/function/splitFunction.java @@ -0,0 +1,87 @@ +/** + * 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.activemq.filter.function; + +import org.apache.activemq.filter.FunctionCallExpression; +import org.apache.activemq.filter.MessageEvaluationContext; + +/** + * Function which splits a string into a list of strings given a regular expression for the separator. + */ + +public class splitFunction implements FilterFunction { + /** + * Check whether the given expression is valid for this function. + * + * @param expr - the expression consisting of a call to this function. + * @return true - if two or three arguments are passed to the function; false - otherwise. + */ + + public boolean isValid(FunctionCallExpression expr) { + if ((expr.getNumArguments() >= 2) && (expr.getNumArguments() <= 3)) + return true; + + return false; + } + + + /** + * Indicate that this function does not return a boolean value. + * + * @param expr - the expression consisting of a call to this function. + * @return false - indicating this filter function never evaluates to a boolean result. + */ + + public boolean returnsBoolean(FunctionCallExpression expr) { + return false; + } + + + /** + * Evaluate the given expression for this function in the given context. A list of zero or more strings + * results from the evaluation. The result of the evaluation of the first argument is split with the regular + * expression which results from the evaluation of the second argument. If a third argument is given, it + * is an integer which limits the split. String#split() performs the split. + *

+ * The first two arguments must be Strings. If a third is given, it must be an Integer. + * + * @param expr - the expression consisting of a call to this function. + * @return List - a list of Strings resulting from the split. + */ + + public Object evaluate(FunctionCallExpression expr, MessageEvaluationContext message_ctx) + throws javax.jms.JMSException { + String src; + String split_pat; + String[] result; + + src = (String) expr.getArgument(0).evaluate(message_ctx); + split_pat = (String) expr.getArgument(1).evaluate(message_ctx); + + if (expr.getNumArguments() > 2) { + Integer limit; + + limit = (Integer) expr.getArgument(2).evaluate(message_ctx); + + result = src.split(split_pat, limit.intValue()); + } else { + result = src.split(split_pat); + } + + return java.util.Arrays.asList(result); + } +} diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorParserTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorParserTest.java index 8993620015..0ab5118a5a 100755 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorParserTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorParserTest.java @@ -16,9 +16,11 @@ */ package org.apache.activemq.selector; +import javax.jms.InvalidSelectorException; import junit.framework.TestCase; import org.apache.activemq.filter.BooleanExpression; +import org.apache.activemq.filter.BooleanFunctionCallExpr; import org.apache.activemq.filter.ComparisonExpression; import org.apache.activemq.filter.Expression; import org.apache.activemq.filter.LogicExpression; @@ -33,6 +35,19 @@ import org.slf4j.LoggerFactory; public class SelectorParserTest extends TestCase { private static final Logger LOG = LoggerFactory.getLogger(SelectorParserTest.class); + public void testFunctionCall() throws Exception { + Object filter = parse("REGEX('sales.*', group)"); + assertTrue("expected type", filter instanceof BooleanFunctionCallExpr); + LOG.info("function exp:" + filter); + + // non existent function + try { + parse("DoesNotExist('sales.*', group)"); + fail("expect ex on non existent function"); + } catch (InvalidSelectorException expected) {} + + } + public void testParseXPath() throws Exception { BooleanExpression filter = parse("XPATH '//title[@lang=''eng'']'"); assertTrue("Created XPath expression", filter instanceof XPathExpression); diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorTest.java index 20a57833ba..8279ae4852 100755 --- a/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorTest.java +++ b/activemq-unit-tests/src/test/java/org/apache/activemq/selector/SelectorTest.java @@ -329,6 +329,24 @@ public class SelectorTest extends TestCase { assertInvalidSelector(message, "=TEST 'test'"); } + public void testFunctionSelector() throws Exception { + Message message = createMessage(); + assertSelector(message, "REGEX('1870414179', SessionserverId)", false); + message.setLongProperty("SessionserverId", 1870414179); + assertSelector(message, "REGEX('1870414179', SessionserverId)", true); + assertSelector(message, "REGEX('[0-9]*', SessionserverId)", true); + assertSelector(message, "REGEX('^[1-8]*$', SessionserverId)", false); + assertSelector(message, "REGEX('^[1-8]*$', SessionserverId)", false); + + assertSelector(message, "INLIST(SPLIT('Tom,Dick,George',','), name)", false); + assertSelector(message, "INLIST(SPLIT('Tom,James,George',','), name)", true); + + assertSelector(message, "INLIST(MAKELIST('Tom','Dick','George'), name)", false); + assertSelector(message, "INLIST(MAKELIST('Tom','James','George'), name)", true); + + assertSelector(message, "REGEX('connection1111', REPLACE(JMSMessageID,':',''))", true); + } + protected Message createMessage() throws JMSException { Message message = createMessage("FOO.BAR"); message.setJMSType("selector-test");