Author: ghenzler
Date: Wed Jan 9 07:53:59 2019
New Revision: 1850823
URL: http://svn.apache.org/viewvc?rev=1850823&view=rev
Log:
FELIX-6012 made JmxAttributeCheck more flexible (more contraints, multiple
attributes that can be checked)
Modified:
felix/trunk/healthcheck/docs/felix-health-checks.md
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/JmxAttributeCheck.java
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintChecker.java
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/JmxAttributeHealthCheckTest.java
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintCheckerTest.java
Modified: felix/trunk/healthcheck/docs/felix-health-checks.md
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/docs/felix-health-checks.md?rev=1850823&r1=1850822&r2=1850823&view=diff
==============================================================================
--- felix/trunk/healthcheck/docs/felix-health-checks.md (original)
+++ felix/trunk/healthcheck/docs/felix-health-checks.md Wed Jan 9 07:53:59 2019
@@ -123,7 +123,7 @@ Memory | org.apache.felix.hc.generalchec
CPU | org.apache.felix.hc.generalchecks.CpuCheck | no | Checks for CPU usage -
`cpuPercentageThresholdWarn` (default 95%) can be set to control what CPU usage
produces status `WARN` (check never results in `CRITICAL`)
Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no |
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses
the CPU usage of each thread via a configurable time period (`samplePeriodInMs`
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN`
about high thread utilisation.
Bundles Started | org.apache.felix.hc.generalchecks.BundlesStartedCheck | yes
| Checks for started bundles - `includesRegex` and `excludesRegex` control what
bundles are checked.
-JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheckk |
yes | Allows to check an arbitrary JMX attribute (using the configured mbean
`mbean.name`'s attribute `attribute.name`) against a given constraint
`attribute.value.constraint`
+JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheckk |
yes | Allows to check an arbitrary JMX attribute (using the configured mbean
`mbean.name`'s attribute `attribute.name`) against a given constraint
`attribute.value.constraint`. Can check multiple attributes by providing
additional config properties with numbers: `mbean2.name`' `attribute2.name`
and `attribute2.value.constraint`.
## Executing Health Checks
Modified:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/JmxAttributeCheck.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/JmxAttributeCheck.java?rev=1850823&r1=1850822&r2=1850823&view=diff
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/JmxAttributeCheck.java
(original)
+++
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/JmxAttributeCheck.java
Wed Jan 9 07:53:59 2019
@@ -18,10 +18,14 @@
package org.apache.felix.hc.generalchecks;
import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+import org.apache.commons.lang3.StringUtils;
import org.apache.felix.hc.api.FormattingResultLog;
import org.apache.felix.hc.api.HealthCheck;
import org.apache.felix.hc.api.Result;
@@ -36,7 +40,7 @@ import org.osgi.service.metatype.annotat
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/** {@link HealthCheck} that checks a single JMX attribute */
+/** {@link HealthCheck} that checks one (or multiple) JMX attribute(s). */
@Component(service = HealthCheck.class, configurationPolicy =
ConfigurationPolicy.REQUIRE)
@Designate(ocd = JmxAttributeCheck.Config.class, factory = true)
public class JmxAttributeCheck implements HealthCheck {
@@ -46,10 +50,6 @@ public class JmxAttributeCheck implement
public static final String HC_NAME = "JMX Attribute";
public static final String HC_LABEL = "Health Check: " + HC_NAME;
-
- private String mbeanName;
- private String attributeName;
- private String constraint;
private Result.Status statusForFailedContraint;
@ObjectClassDefinition(name = HC_LABEL, description = "Checks the value of
a single JMX attribute.")
@@ -64,7 +64,7 @@ public class JmxAttributeCheck implement
@AttributeDefinition(name = "MBean Name", description = "The name of
the MBean to retrieve the attribute to be checked from.")
String mbean_name() default "";
- @AttributeDefinition(name = "Attribute Name", description = "The name
of the MBean attribute to check against the constraing.")
+ @AttributeDefinition(name = "Attribute Name", description = "The name
of the MBean attribute to check against the constraint.")
String attribute_name() default "";
@AttributeDefinition(name = "Attribute Constraint", description =
"Constraint on the MBean attribute value. If simple value, uses equals. For
strings constraints like 'CONTAINS mystr', 'STARTS_WITH mystr' or 'ENDS_WITH
mystr' can be used, for numbers constraints like '> 4', '= 7', '< 9' or
'between 3 and 7' work.")
@@ -77,40 +77,111 @@ public class JmxAttributeCheck implement
String webconsole_configurationFactory_nameHint() default "JMX MBean
{mbean.name} Attribute '{attribute.name}' constraint:
{attribute.value.constraint}";
}
+ private List<AttributeConstraintConfig> attributeConstraintConfigs;
@Activate
- protected void activate(final Config config) {
- mbeanName = config.mbean_name();
- attributeName = config.attribute_name();
- constraint = config.attribute_value_constraint();
+ protected void activate(final Config config, final Map<String,Object>
rawConfig) {
statusForFailedContraint = config.statusForFailedContraint();
- LOG.info("Activated JMX Attribute HC for mbeanName {} attributeName={}
constraint={} statusForFailedContraint={}", mbeanName, attributeName,
constraint, statusForFailedContraint);
+ attributeConstraintConfigs = AttributeConstraintConfig.load(config,
rawConfig);
+
+ LOG.info("Activated JMX Attribute HC with statusForFailedContraint={}
and attribute constraint config(s):", statusForFailedContraint);
+ for (AttributeConstraintConfig attributeConstraintConfig :
attributeConstraintConfigs) {
+ LOG.info(attributeConstraintConfig.toString());
+ }
}
+
@Override
public Result execute() {
- final FormattingResultLog resultLog = new FormattingResultLog();
- resultLog.debug("Checking {} / {} with constraint {}", mbeanName,
attributeName, constraint);
+ FormattingResultLog resultLog = new FormattingResultLog();
+ for (AttributeConstraintConfig attributeConstraintConfig :
attributeConstraintConfigs) {
+ checkAttributeConstraint(resultLog, attributeConstraintConfig);
+ }
+ return new Result(resultLog);
+ }
+
+ private void checkAttributeConstraint(final FormattingResultLog resultLog,
AttributeConstraintConfig attributeConstraintConfig) {
+ resultLog.debug("Checking {}", attributeConstraintConfig);
try {
final MBeanServer jmxServer =
ManagementFactory.getPlatformMBeanServer();
- final ObjectName objectName = new ObjectName(mbeanName);
+ final ObjectName objectName = new
ObjectName(attributeConstraintConfig.mbeanName);
if (jmxServer.queryNames(objectName, null).size() == 0) {
resultLog.warn("MBean not found: {}", objectName);
} else {
- final Object value = jmxServer.getAttribute(objectName,
attributeName);
- resultLog.debug("{} {} returns {}", mbeanName, attributeName,
value);
- boolean matches = new SimpleConstraintChecker().check(value,
constraint);
- String baseMsg = "JMX attribute "+mbeanName+" ->
'"+attributeName+"': Value [" + value + "] ";
+ final Object value = jmxServer.getAttribute(objectName,
attributeConstraintConfig.attributeName);
+ resultLog.debug("{} {} returns {}",
attributeConstraintConfig.mbeanName, attributeConstraintConfig.attributeName,
value);
+ boolean matches = new SimpleConstraintChecker().check(value,
attributeConstraintConfig.attributeValueConstraint);
+ String baseMsg = "JMX attribute
"+attributeConstraintConfig.mbeanName+" ->
'"+attributeConstraintConfig.attributeName+"': Value [" + value + "] ";
if (matches) {
- resultLog.add(new ResultLog.Entry(Result.Status.OK,
baseMsg+"matches constraint [" + constraint + "]"));
+ resultLog.add(new ResultLog.Entry(Result.Status.OK,
baseMsg+"matches constraint [" +
attributeConstraintConfig.attributeValueConstraint + "]"));
} else {
- resultLog.add(new ResultLog.Entry(
statusForFailedContraint, baseMsg+"does not match constraint [" + constraint +
"]"));
+ resultLog.add(new ResultLog.Entry(
statusForFailedContraint, baseMsg+"does not match constraint [" +
attributeConstraintConfig.attributeValueConstraint + "]"));
}
}
} catch (Exception e) {
- LOG.warn("JMX attribute {}.{} check failed: {}", mbeanName,
attributeName, e.getMessage(), e);
- resultLog.healthCheckError("JMX attribute check failed: {}", e);
+ LOG.warn("JMX check {} failed: {}", attributeConstraintConfig,
e.getMessage(), e);
+ resultLog.healthCheckError("JMX attribute check failed: {}",
attributeConstraintConfig, e);
}
- return new Result(resultLog);
+ }
+
+
+ private static class AttributeConstraintConfig {
+
+ public static final String PROP_MBEAN = "mbean";
+ public static final String PROP_ATTRIBUTE = "attribute";
+
+ public static final String SUFFIX_NAME = ".name";
+ public static final String SUFFIX_VALUE_CONSTRAINT =
".value.constraint";
+
+ private static List<AttributeConstraintConfig> load(final Config
config, final Map<String, Object> rawConfig) {
+ List<AttributeConstraintConfig> attributeConstraintConfigs = new
ArrayList<AttributeConstraintConfig>();
+
+ // first attribute via metatype
+ attributeConstraintConfigs.add(new
AttributeConstraintConfig(config.mbean_name(),
config.attribute_name(),config.attribute_value_constraint()));
+
+ // additional attributes possible via naming scheme "mbean2.name"
/ "attribute2.name" ...
+ int attributeCounter = 2;
+ while(AttributeConstraintConfig.hasConfig(rawConfig,
attributeCounter)) {
+ attributeConstraintConfigs.add(new
AttributeConstraintConfig(rawConfig, attributeCounter));
+ attributeCounter++;
+ }
+ return attributeConstraintConfigs;
+ }
+
+ private static String getAttributePropName(int attributeCounter) {
+ return PROP_ATTRIBUTE + attributeCounter + SUFFIX_NAME;
+ }
+
+ private static boolean hasConfig(Map<String,Object> rawConfig, int
attributeCounter) {
+ return
rawConfig.containsKey(getAttributePropName(attributeCounter));
+ }
+
+ final String mbeanName;
+
+ final String attributeName;
+ final String attributeValueConstraint;
+
+ public AttributeConstraintConfig(String mbeanName, String
attributeName, String attributeValueConstraint) {
+ this.mbeanName = mbeanName;
+ this.attributeName = attributeName;
+ this.attributeValueConstraint = attributeValueConstraint;
+ }
+
+ public AttributeConstraintConfig(Map<String,Object> rawConfig, int
attributeCounter) {
+ String propNameAttribute = getAttributePropName(attributeCounter);
+ String defaultMBeanName = (String) rawConfig.get(PROP_MBEAN +
SUFFIX_NAME);
+ String mBeanName = (String) rawConfig.get(PROP_MBEAN +
attributeCounter + SUFFIX_NAME);
+ this.mbeanName = StringUtils.defaultIfBlank(mBeanName,
defaultMBeanName);
+ this.attributeName = (String) rawConfig.get(propNameAttribute);
+ this.attributeValueConstraint = (String)
rawConfig.get(PROP_ATTRIBUTE + attributeCounter + SUFFIX_VALUE_CONSTRAINT);
+ if(StringUtils.isAnyBlank(mbeanName, attributeName,
attributeValueConstraint)) {
+ throw new IllegalArgumentException("Invalid JmxAttributeCheck
config for property "+mbeanName+" -> "+propNameAttribute+": "+toString());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "JMX attribute "+mbeanName+" -> '"+attributeName+"':
Constraint: "+attributeValueConstraint;
+ };
}
}
Modified:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintChecker.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintChecker.java?rev=1850823&r1=1850822&r2=1850823&view=diff
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintChecker.java
(original)
+++
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintChecker.java
Wed Jan 9 07:53:59 2019
@@ -17,15 +17,25 @@
*/
package org.apache.felix.hc.generalchecks.util;
+import java.util.Calendar;
+
import org.apache.commons.lang3.StringUtils;
-/** Simple check of numeric values against expressions like < N, > N,
between two values etc. See the SimpleConstraintCheckerTest for
+/** Simple check of values against expressions like < N, > N, between
two values etc. See the SimpleConstraintCheckerTest for
* examples. */
public class SimpleConstraintChecker {
+ public static final String GREATER_THAN = ">";
+ public static final String LESS_THAN = "<";
+ public static final String EQUALS = "=";
+
+ public static final String BETWEEN = "between";
+ public static final String AND = "and";
public static final String CONTAINS = "contains";
public static final String STARTS_WITH = "starts_with";
public static final String ENDS_WITH = "ends_with";
+ public static final String MATCHES = "matches"; // regex
+ public static final String OLDER_THAN = "older_than"; // for unix
timestamps
/** Check value against expression and report to result
* @param statusForFailedContraint */
@@ -37,41 +47,95 @@ public class SimpleConstraintChecker {
final String stringValue = inputValue == null ? "" :
inputValue.toString();
- if (constraint == null || constraint.trim().length() == 0) {
+ if (StringUtils.isBlank(constraint)) {
throw new IllegalArgumentException("Empty constraint, cannot
evaluate");
}
- final String[] parts = constraint.split(" ");
+ String[] parts = constraint.split(" +");
boolean matches = false;
-
- if (constraint.startsWith(">") && parts.length == 2) {
- final int value = Integer.valueOf(stringValue).intValue();
- matches = value > Integer.valueOf(parts[1]);
-
- } else if (constraint.startsWith("<") && parts.length == 2) {
- final int value = Integer.valueOf(stringValue).intValue();
- matches = value < Integer.valueOf(parts[1]);
-
- } else if (parts.length == 4 && "between".equalsIgnoreCase(parts[0])
&& "and".equalsIgnoreCase(parts[2])) {
- final int value = Integer.valueOf(stringValue).intValue();
- final int lowerBound = Integer.valueOf(parts[1]);
- final int upperBound = Integer.valueOf(parts[3]);
+ boolean inverseResult = false;
+ if(parts[0].equalsIgnoreCase("not")) {
+ inverseResult = true;
+ String[] newParts = new String[parts.length - 1];
+ System.arraycopy(parts, 1, newParts, 0, newParts.length);
+ parts = newParts;
+ }
+
+ if (parts[0].equals(GREATER_THAN) && parts.length == 2) {
+ long value = Long.valueOf(stringValue).longValue();
+ matches = value > Long.valueOf(parts[1]);
+
+ } else if (parts[0].equals(LESS_THAN) && parts.length == 2) {
+ long value = Long.valueOf(stringValue).longValue();
+ matches = value < Long.valueOf(parts[1]);
+
+ } else if (parts[0].equals(EQUALS) && parts.length == 2) {
+ long value = Long.valueOf(stringValue).longValue();
+ matches = value == Long.valueOf(parts[1]).longValue();
+
+ } else if (parts.length == 4 && BETWEEN.equalsIgnoreCase(parts[0]) &&
AND.equalsIgnoreCase(parts[2])) {
+ long value = Long.valueOf(stringValue).longValue();
+ long lowerBound = Long.valueOf(parts[1]).longValue();
+ long upperBound = Long.valueOf(parts[3]).longValue();
matches = value > lowerBound && value < upperBound;
} else if (parts.length > 1 && CONTAINS.equalsIgnoreCase(parts[0])) {
- final String pattern = StringUtils.join(parts, " ", 1,
parts.length);
+ String pattern = StringUtils.join(parts, " ", 1, parts.length);
matches = stringValue.contains(pattern);
} else if (parts.length > 1 && STARTS_WITH.equalsIgnoreCase(parts[0]))
{
- final String pattern = StringUtils.join(parts, " ", 1,
parts.length);
+ String pattern = StringUtils.join(parts, " ", 1, parts.length);
matches = stringValue.startsWith(pattern);
} else if (parts.length > 1 && ENDS_WITH.equalsIgnoreCase(parts[0])) {
- final String pattern = StringUtils.join(parts, " ", 1,
parts.length);
+ String pattern = StringUtils.join(parts, " ", 1, parts.length);
matches = stringValue.endsWith(pattern);
+ } else if (parts.length > 1 && MATCHES.equalsIgnoreCase(parts[0])) {
+ String pattern = StringUtils.join(parts, " ", 1, parts.length);
+ matches = stringValue.matches(pattern);
+ } else if (parts.length > 1 && OLDER_THAN.equalsIgnoreCase(parts[0])
&& parts.length == 3) {
+ int unit = stringToUnit(parts[2]);
+ long timestamp = Long.valueOf(stringValue).longValue();
+ int timeDiff = Integer.valueOf(parts[1]).intValue();
+
+ Calendar cal = Calendar.getInstance();
+ cal.add(unit, -timeDiff);
+ long compareTimestamp = cal.getTime().getTime();
+
+ matches = timestamp < compareTimestamp;
+
} else {
- matches = constraint.equals(stringValue);
+ matches = StringUtils.join(parts, "").equals(stringValue);
}
- return matches;
+ boolean result = matches ^ inverseResult;
+ return result;
+
+ }
+ private int stringToUnit(String unitString) {
+ int unit;
+ switch(unitString) {
+ case "ms":
+ unit = Calendar.MILLISECOND; break;
+ case "min":
+ case "minute":
+ case "minutes":
+ unit = Calendar.MINUTE; break;
+ case "h":
+ case "hour":
+ case "hours":
+ unit = Calendar.HOUR; break;
+ case "d":
+ case "day":
+ case "days":
+ unit = Calendar.DAY_OF_YEAR; break;
+ case "s":
+ case "sec":
+ case "second":
+ case "seconds":
+ unit = Calendar.SECOND; break;
+ default:
+ throw new IllegalArgumentException("Unexpected unit
'"+unitString+"'");
+ }
+ return unit;
}
}
\ No newline at end of file
Modified:
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/JmxAttributeHealthCheckTest.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/JmxAttributeHealthCheckTest.java?rev=1850823&r1=1850822&r2=1850823&view=diff
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/JmxAttributeHealthCheckTest.java
(original)
+++
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/JmxAttributeHealthCheckTest.java
Wed Jan 9 07:53:59 2019
@@ -21,6 +21,8 @@ import static org.junit.Assert.assertEqu
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.HashMap;
+
import org.apache.felix.hc.api.Result;
import org.apache.felix.hc.generalchecks.JmxAttributeCheck;
import org.junit.Test;
@@ -35,7 +37,7 @@ public class JmxAttributeHealthCheckTest
when(configuration.attribute_name()).thenReturn(attributeName);
when(configuration.attribute_value_constraint()).thenReturn(constraint);
- hc.activate(configuration);
+ hc.activate(configuration, new HashMap<String,Object>());
final Result r = hc.execute();
assertEquals("Expected result " + expected, expected, r.isOk());
@@ -50,4 +52,5 @@ public class JmxAttributeHealthCheckTest
public void testJmxAttributeNoMatch() {
assertJmxValue("java.lang:type=ClassLoading", "LoadedClassCount", "<
10", false);
}
+
}
Modified:
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintCheckerTest.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintCheckerTest.java?rev=1850823&r1=1850822&r2=1850823&view=diff
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintCheckerTest.java
(original)
+++
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/SimpleConstraintCheckerTest.java
Wed Jan 9 07:53:59 2019
@@ -20,6 +20,9 @@ package org.apache.felix.hc.generalcheck
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.util.Calendar;
+import java.util.Date;
+
import org.junit.Before;
import org.junit.Test;
@@ -69,6 +72,12 @@ public class SimpleConstraintCheckerTest
assertFalse(checker.check(null, "foo"));
}
+
+ @Test
+ public void testNumberEquals() {
+ assertTrue(checker.check("7", "= 7"));
+ }
+
@Test
public void testNullNotGreater() {
assertFalse(checker.check(null, "> 2"));
@@ -95,6 +104,12 @@ public class SimpleConstraintCheckerTest
}
@Test
+ public void testNot() {
+ assertTrue(checker.check("5", "not < 2"));
+ assertFalse(checker.check("5", "not < 6"));
+ }
+
+ @Test
public void testBetweenA() {
assertTrue(checker.check("5", "between 2 and 6"));
}
@@ -157,6 +172,30 @@ public class SimpleConstraintCheckerTest
@Test
public void testEndsWithB() {
assertFalse(checker.check("This is a NICE TOUCH ok?", "ENDS_WITH is"));
- }
+ }
+
+ @Test
+ public void testMatches() {
+ assertTrue(checker.check("testABCtest", "matches .*ABC.*"));
+ assertFalse(checker.check("testABCtest", "matches .*XYZ.*"));
+ assertTrue(checker.check("testABCtest", "not matches .*XYZ.*"));
+ assertTrue(checker.check("2.1.0", "matches ^2\\.[1-9]\\.[0-9]+"));
+ }
+
+ @Test
+ public void testOlderThan() {
+ long timestampNow = new Date().getTime();
+ assertFalse(checker.check(new Long(timestampNow), "OLDER_THAN 5 sec"));
+
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.MINUTE, -55);
+
+ assertTrue(checker.check(cal.getTime().getTime()+"", "OLDER_THAN 53
min"));
+ assertFalse(checker.check(cal.getTime().getTime()+"", "OLDER_THAN 57
min"));
+ assertTrue(checker.check(cal.getTime().getTime()+"", "NOT OLDER_THAN
57 min"));
+ assertFalse(checker.check(cal.getTime().getTime()+"", "OLDER_THAN 1
hour"));
+ assertFalse(checker.check(cal.getTime().getTime()+"", "OLDER_THAN 1
days"));
+ assertTrue(checker.check(cal.getTime().getTime()+"", "OLDER_THAN 100
ms"));
+ }
}