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 &lt; N, &gt; N, 
between two values etc. See the SimpleConstraintCheckerTest for
+/** Simple check of values against expressions like &lt; N, &gt; 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"));
+    }
     
 }


Reply via email to