This commit is contained in:
Justin Bertram 2022-09-19 14:45:06 -05:00
commit 9a0669e282
No known key found for this signature in database
GPG Key ID: F41830B875BB8633
8 changed files with 497 additions and 162 deletions

View File

@ -221,6 +221,9 @@ public abstract class ComparisonExpression extends BinaryExpression implements B
// Iff one of the values is null
if (lv == null ^ rv == null) {
if (lv == null) {
return null;
}
return Boolean.FALSE;
}
if (lv == rv || lv.equals(rv)) {
@ -232,6 +235,31 @@ public abstract class ComparisonExpression extends BinaryExpression implements B
return Boolean.FALSE;
}
@Override
public boolean matches(Filterable message) throws FilterException {
Object lv = left.evaluate(message);
Object rv = right.evaluate(message);
// If one of the values is null
if (lv == null ^ rv == null) {
return false;
}
if (lv == rv || lv.equals(rv)) {
return true;
}
if (lv.getClass() == rv.getClass()) {
// same class, but 'equals' return false, and they are not the same object
// there is no point in doing 'compare'
// this case happens often while comparing non equals Strings
return false;
}
if (lv instanceof Comparable && rv instanceof Comparable) {
Boolean compareResult = compare((Comparable) lv, (Comparable) rv);
return compareResult != null && compareResult;
}
return false;
}
@Override
protected boolean asBoolean(int answer) {
return answer == 0;

View File

@ -16,78 +16,170 @@
*/
package org.apache.activemq.artemis.selector.filter;
import java.util.ArrayList;
import java.util.List;
/**
* A filter performing a comparison of two objects
* A sequence of expressions, to be combined with OR or AND conjunctions.
*
* @version $Revision: 1.2 $
*/
public abstract class LogicExpression extends BinaryExpression implements BooleanExpression {
public abstract class LogicExpression implements BooleanExpression {
protected final List<BooleanExpression> expressions = new ArrayList<>(2);
private LogicExpression(BooleanExpression lvalue, BooleanExpression rvalue) {
expressions.add(lvalue);
expressions.add(rvalue);
}
protected void addExpression(BooleanExpression expression) {
expressions.add(expression);
}
public BooleanExpression getLeft() {
if (expressions.size() == 2) {
return expressions.get(0);
}
throw new IllegalStateException("This expression is not binary: " + this);
}
public BooleanExpression getRight() {
if (expressions.size() == 2) {
return expressions.get(1);
}
throw new IllegalStateException("This expression is not binary: " + this);
}
/**
* @param left
* @param right
* Returns the symbol that represents this binary expression. For example, addition is
* represented by "+"
*
* @return
*/
public LogicExpression(BooleanExpression left, BooleanExpression right) {
super(left, right);
public abstract String getExpressionSymbol();
@Override
public String toString() {
if (expressions.size() == 2) {
return "( " + expressions.get(0) + " " + getExpressionSymbol() + " " + expressions.get(1) + " )";
}
StringBuilder result = new StringBuilder("(");
int count = 0;
for (BooleanExpression expression : expressions) {
if (count++ > 0) {
result.append(" " + getExpressionSymbol() + " ");
}
result.append(expression.toString());
}
result.append(")");
return result.toString();
}
public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue) {
return new LogicExpression(lvalue, rvalue) {
@Override
public Object evaluate(Filterable message) throws FilterException {
Boolean lv = (Boolean) left.evaluate(message);
// Can we do an OR shortcut??
if (lv != null && lv.booleanValue()) {
return Boolean.TRUE;
}
Boolean rv = (Boolean) right.evaluate(message);
return rv == null ? null : rv;
}
@Override
public String getExpressionSymbol() {
return "OR";
}
};
if (lvalue instanceof ORExpression) {
ORExpression orExpression = (ORExpression) lvalue;
orExpression.addExpression(rvalue);
return orExpression;
} else {
return new ORExpression(lvalue, rvalue);
}
}
public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue) {
return new LogicExpression(lvalue, rvalue) {
@Override
public Object evaluate(Filterable message) throws FilterException {
Boolean lv = (Boolean) left.evaluate(message);
// Can we do an AND shortcut??
if (lv == null) {
return null;
}
if (!lv.booleanValue()) {
return Boolean.FALSE;
}
Boolean rv = (Boolean) right.evaluate(message);
return rv == null ? null : rv;
}
@Override
public String getExpressionSymbol() {
return "AND";
}
};
if (lvalue instanceof ANDExpression) {
ANDExpression orExpression = (ANDExpression) lvalue;
orExpression.addExpression(rvalue);
return orExpression;
} else {
return new ANDExpression(lvalue, rvalue);
}
}
@Override
public abstract Object evaluate(Filterable message) throws FilterException;
@Override
public boolean matches(Filterable message) throws FilterException {
Object object = evaluate(message);
return object == Boolean.TRUE;
public abstract boolean matches(Filterable message) throws FilterException;
private static class ORExpression extends LogicExpression {
ORExpression(BooleanExpression lvalue, BooleanExpression rvalue) {
super(lvalue, rvalue);
}
@Override
public Object evaluate(Filterable message) throws FilterException {
boolean someNulls = false;
for (BooleanExpression expression : expressions) {
Boolean lv = (Boolean)expression.evaluate(message);
if (lv != null && lv.booleanValue()) {
return Boolean.TRUE;
}
if (lv == null) {
someNulls = true;
}
}
if (someNulls) {
return null;
}
return Boolean.FALSE;
}
@Override
public boolean matches(Filterable message) throws FilterException {
for (BooleanExpression expression : expressions) {
boolean lv = expression.matches(message);
if (lv) {
return true;
}
}
return false;
}
@Override
public String getExpressionSymbol() {
return "OR";
}
}
private static class ANDExpression extends LogicExpression {
ANDExpression(BooleanExpression lvalue, BooleanExpression rvalue) {
super(lvalue, rvalue);
}
@Override
public Object evaluate(Filterable message) throws FilterException {
boolean someNulls = false;
for (BooleanExpression expression : expressions) {
Boolean lv = (Boolean)expression.evaluate(message);
if (lv != null && !lv.booleanValue()) {
return Boolean.FALSE;
}
if (lv == null) {
someNulls = true;
}
}
if (someNulls) {
return null;
}
return Boolean.TRUE;
}
@Override
public boolean matches(Filterable message) throws FilterException {
for (BooleanExpression expression : expressions) {
boolean lv = expression.matches(message);
if (!lv) {
return false;
}
}
return true;
}
@Override
public String getExpressionSymbol() {
return "AND";
}
}
}

View File

@ -139,6 +139,16 @@ public abstract class UnaryExpression implements Expression {
return !lvalue.booleanValue();
}
@Override
public boolean matches(Filterable message) throws FilterException {
Boolean lvalue = (Boolean) right.evaluate(message);
if (lvalue == null) {
// NOT NULL returns NULL that eventually fails the selector
return false;
}
return !lvalue;
}
@Override
public String getExpressionSymbol() {
return "NOT";

View File

@ -0,0 +1,115 @@
/*
* 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.artemis.selector;
import java.util.HashMap;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.filter.Filterable;
public class MockMessage implements Filterable {
HashMap<String, Object> properties = new HashMap<>();
private String text;
private Object destination;
private String messageId;
private String type;
private Object localConnectionId;
public void setDestination(Object destination) {
this.destination = destination;
}
public void setJMSMessageID(String messageId) {
this.messageId = messageId;
}
public void setJMSType(String type) {
this.type = type;
}
public void setText(String text) {
this.text = text;
}
public void setBooleanProperty(String key, boolean value) {
properties.put(key, value);
}
public void setStringProperty(String key, String value) {
properties.put(key, value);
}
public void setByteProperty(String key, byte value) {
properties.put(key, value);
}
public void setDoubleProperty(String key, double value) {
properties.put(key, value);
}
public void setFloatProperty(String key, float value) {
properties.put(key, value);
}
public void setLongProperty(String key, long value) {
properties.put(key, value);
}
public void setIntProperty(String key, int value) {
properties.put(key, value);
}
public void setShortProperty(String key, short value) {
properties.put(key, value);
}
public void setObjectProperty(String key, Object value) {
properties.put(key, value);
}
@Override
public <T> T getBodyAs(Class<T> type) throws FilterException {
if (type == String.class) {
return type.cast(text);
}
return null;
}
@Override
public Object getProperty(SimpleString name) {
String stringName = name.toString();
if ("JMSType".equals(stringName)) {
return type;
}
if ("JMSMessageID".equals(stringName)) {
return messageId;
}
return properties.get(stringName);
}
public Object getDestination() {
return destination;
}
@Override
public Object getLocalConnectionId() {
return localConnectionId;
}
}

View File

@ -16,117 +16,24 @@
*/
package org.apache.activemq.artemis.selector;
import java.util.HashMap;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.selector.filter.BooleanExpression;
import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.filter.Filterable;
import org.apache.activemq.artemis.selector.impl.SelectorParser;
import org.junit.Assert;
import org.junit.Test;
public class SelectorTest {
class MockMessage implements Filterable {
HashMap<String, Object> properties = new HashMap<>();
private String text;
private Object destination;
private String messageId;
private String type;
private Object localConnectionId;
public void setDestination(Object destination) {
this.destination = destination;
}
public void setJMSMessageID(String messageId) {
this.messageId = messageId;
}
public void setJMSType(String type) {
this.type = type;
}
public void setText(String text) {
this.text = text;
}
public void setBooleanProperty(String key, boolean value) {
properties.put(key, value);
}
public void setStringProperty(String key, String value) {
properties.put(key, value);
}
public void setByteProperty(String key, byte value) {
properties.put(key, value);
}
public void setDoubleProperty(String key, double value) {
properties.put(key, value);
}
public void setFloatProperty(String key, float value) {
properties.put(key, value);
}
public void setLongProperty(String key, long value) {
properties.put(key, value);
}
public void setIntProperty(String key, int value) {
properties.put(key, value);
}
public void setShortProperty(String key, short value) {
properties.put(key, value);
}
public void setObjectProperty(String key, Object value) {
properties.put(key, value);
}
@Override
public <T> T getBodyAs(Class<T> type) throws FilterException {
if (type == String.class) {
return type.cast(text);
}
return null;
}
@Override
public Object getProperty(SimpleString name) {
String stringName = name.toString();
if ("JMSType".equals(stringName)) {
return type;
}
if ("JMSMessageID".equals(stringName)) {
return messageId;
}
return properties.get(stringName);
}
public Object getDestination() {
return destination;
}
@Override
public Object getLocalConnectionId() {
return localConnectionId;
}
}
@Test
public void testBooleanSelector() throws Exception {
MockMessage message = createMessage();
assertSelector(message, "(trueProp OR falseProp) AND trueProp", true);
assertSelector(message, "(trueProp OR falseProp) AND falseProp", false);
assertSelector(message, "(falseProp OR falseProp OR falseProp OR falseProp OR falseProp OR falseProp OR trueProp)", true);
assertSelector(message, "(falseProp OR falseProp OR falseProp OR falseProp OR falseProp OR falseProp OR falseProp)", false);
assertSelector(message, "(trueProp AND trueProp AND trueProp AND trueProp AND trueProp AND trueProp AND falseProp)", false);
assertSelector(message, "(trueProp AND trueProp AND trueProp AND trueProp AND trueProp AND trueProp AND trueProp)", true);
}
@Test

View File

@ -0,0 +1,175 @@
/**
* 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.artemis.selector;
import org.apache.activemq.artemis.selector.filter.BooleanExpression;
import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.impl.SelectorParser;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class UnknownHandlingSelectorTest {
private MockMessage message;
@Before
public void setUp() throws Exception {
message = new MockMessage();
message.setDestination("FOO.BAR");
message.setJMSType("selector-test");
message.setJMSMessageID("connection:1:1:1:1");
message.setBooleanProperty("trueProp", true);
message.setBooleanProperty("falseProp", false);
message.setObjectProperty("nullProp", null);
}
/**
* | NOT
* +------+------
* | T | F
* | F | T
* | U | U
* +------+-------
*/
@Test
public void notEvaluation() throws Exception {
assertSelector("not(trueProp)", false);
assertSelector("not(falseProp)", true);
assertSelector("not(unknownProp)", false);
}
/**
* | AND | T | F | U
* +------+-------+-------+-------
* | T | T | F | U
* | F | F | F | F
* | U | U | F | U
* +------+-------+-------+-------
*/
@Test
public void andEvaluation() throws Exception {
assertSelectorEvaluatesToTrue("trueProp AND trueProp");
assertSelectorEvaluatesToFalse("trueProp AND falseProp");
assertSelectorEvaluatesToFalse("falseProp AND trueProp");
assertSelectorEvaluatesToFalse("falseProp AND falseProp");
assertSelectorEvaluatesToFalse("falseProp AND unknownProp");
assertSelectorEvaluatesToFalse("unknownProp AND falseProp");
assertSelectorEvaluatesToUnknown("trueProp AND unknownProp");
assertSelectorEvaluatesToUnknown("unknownProp AND trueProp");
assertSelectorEvaluatesToUnknown("unknownProp AND unknownProp");
}
/**
* | OR | T | F | U
* +------+-------+-------+--------
* | T | T | T | T
* | F | T | F | U
* | U | T | U | U
* +------+-------+-------+-------
*/
@Test
public void orEvaluation() throws Exception {
assertSelectorEvaluatesToTrue("trueProp OR trueProp");
assertSelectorEvaluatesToTrue("trueProp OR falseProp");
assertSelectorEvaluatesToTrue("falseProp OR trueProp");
assertSelectorEvaluatesToTrue("trueProp OR unknownProp");
assertSelectorEvaluatesToTrue("unknownProp OR trueProp");
assertSelectorEvaluatesToFalse("falseProp OR falseProp");
assertSelectorEvaluatesToUnknown("falseProp OR unknownProp");
assertSelectorEvaluatesToUnknown("unknownProp OR falseProp");
assertSelectorEvaluatesToUnknown("unknownProp OR unknownProp");
}
@Test
public void comparisonWithUnknownShouldEvaluateToUnknown() throws Exception {
assertSelectorEvaluatesToUnknown("unknownProp = 0");
assertSelectorEvaluatesToUnknown("unknownProp > 0");
assertSelectorEvaluatesToUnknown("unknownProp >= 0");
assertSelectorEvaluatesToUnknown("unknownProp < 0");
assertSelectorEvaluatesToUnknown("unknownProp <= 0");
assertSelectorEvaluatesToUnknown("unknownProp <> 0");
assertSelectorEvaluatesToUnknown("unknownProp LIKE 'zero'");
assertSelectorEvaluatesToUnknown("unknownProp NOT LIKE 'zero'");
assertSelectorEvaluatesToUnknown("unknownProp IN ('zero')");
assertSelectorEvaluatesToUnknown("unknownProp NOT IN ('zero')");
assertSelectorEvaluatesToUnknown("unknownProp BETWEEN 1 AND 2");
assertSelectorEvaluatesToUnknown("unknownProp NOT BETWEEN 1 AND 2");
}
@Test
public void comparisonWithNullPropShouldEvaluateToUnknown() throws Exception {
assertSelectorEvaluatesToUnknown("nullProp = 0");
assertSelectorEvaluatesToUnknown("nullProp > 0");
assertSelectorEvaluatesToUnknown("nullProp >= 0");
assertSelectorEvaluatesToUnknown("nullProp < 0");
assertSelectorEvaluatesToUnknown("nullProp <= 0");
assertSelectorEvaluatesToUnknown("nullProp <> 0");
assertSelectorEvaluatesToUnknown("nullProp LIKE 'zero'");
assertSelectorEvaluatesToUnknown("nullProp NOT LIKE 'zero'");
assertSelectorEvaluatesToUnknown("nullProp IN ('zero')");
assertSelectorEvaluatesToUnknown("nullProp NOT IN ('zero')");
assertSelectorEvaluatesToUnknown("nullProp BETWEEN 1 AND 2");
assertSelectorEvaluatesToUnknown("nullProp NOT BETWEEN 1 AND 2");
}
@Test
public void isNullIsNotNull() throws Exception {
assertSelectorEvaluatesToTrue("unknownProp IS NULL");
assertSelectorEvaluatesToTrue("nullProp IS NULL");
assertSelectorEvaluatesToFalse("trueProp IS NULL");
assertSelectorEvaluatesToFalse("unknownProp IS NOT NULL");
assertSelectorEvaluatesToFalse("nullProp IS NOT NULL");
assertSelectorEvaluatesToTrue("trueProp IS NOT NULL");
}
@Test
public void arithmeticWithNull() throws Exception {
assertSelectorEvaluatesToUnknown("-unknownProp = 0");
assertSelectorEvaluatesToUnknown("+unknownProp = 0");
assertSelectorEvaluatesToUnknown("unknownProp * 2 = 0");
assertSelectorEvaluatesToUnknown("unknownProp / 2 = 0");
assertSelectorEvaluatesToUnknown("unknownProp + 2 = 0");
assertSelectorEvaluatesToUnknown("unknownProp - 2 = 0");
}
protected void assertSelectorEvaluatesToUnknown(String selector) throws FilterException {
assertSelector(selector, false);
assertSelector(not(selector), false);
}
protected void assertSelectorEvaluatesToTrue(String selector) throws FilterException {
assertSelector(selector, true);
assertSelector(not(selector), false);
}
protected void assertSelectorEvaluatesToFalse(String selector) throws FilterException {
assertSelector(selector, false);
assertSelector(not(selector), true);
}
protected void assertSelector(String text, boolean expected) throws FilterException {
BooleanExpression selector = SelectorParser.parse(text);
Assert.assertTrue("Created a valid selector", selector != null);
boolean value = selector.matches(message);
Assert.assertEquals("Selector for: " + text, expected, value);
}
private static String not(String selector) {
return "not(" + selector + ")";
}
}

View File

@ -251,29 +251,31 @@ public class ClusterConnectionBridge extends BridgeImpl {
SimpleString notifQueueName = new SimpleString(qName);
SimpleString filter = new SimpleString(ManagementHelper.HDR_BINDING_TYPE + "<>" +
BindingType.DIVERT.toInt() +
SimpleString filter = new SimpleString("(" + ManagementHelper.HDR_BINDING_TYPE + " <> " + BindingType.DIVERT.toInt() +
" OR "
+ ManagementHelper.HDR_BINDING_TYPE + " IS NULL)" +
" AND " +
ManagementHelper.HDR_NOTIFICATION_TYPE +
" IN ('" +
CoreNotificationType.SESSION_CREATED +
"','" +
"', '" +
CoreNotificationType.BINDING_ADDED +
"','" +
"', '" +
CoreNotificationType.BINDING_REMOVED +
"','" +
"', '" +
CoreNotificationType.CONSUMER_CREATED +
"','" +
"', '" +
CoreNotificationType.CONSUMER_CLOSED +
"','" +
"', '" +
CoreNotificationType.PROPOSAL +
"','" +
"', '" +
CoreNotificationType.PROPOSAL_RESPONSE +
"','" +
"', '" +
CoreNotificationType.UNPROPOSAL +
"') AND " +
"')" +
" AND " +
ManagementHelper.HDR_DISTANCE +
"<" +
" < " +
flowRecord.getMaxHops() +
" AND (" +
createSelectorFromAddress(appendIgnoresToFilter(flowRecord.getAddress())) +

View File

@ -170,9 +170,15 @@ public class FilterTest extends SilentTestCase {
@Test
public void testDifferentNullString() throws Exception {
filter = FilterImpl.createFilter(new SimpleString("prop <> 'foo'"));
Assert.assertFalse(filter.match(message));
filter = FilterImpl.createFilter(new SimpleString("prop <> 'foo' OR prop IS NULL"));
Assert.assertTrue(filter.match(message));
filter = FilterImpl.createFilter(new SimpleString("NOT (prop = 'foo')"));
Assert.assertFalse(filter.match(message));
filter = FilterImpl.createFilter(new SimpleString("NOT (prop = 'foo') OR prop IS NULL"));
Assert.assertTrue(filter.match(message));
filter = FilterImpl.createFilter(new SimpleString("prop <> 'foo'"));