Ticket 717: Max-age-limit in composite rules
authorMats Vernersson <mats.vernersson@smhi.se>
Wed, 28 Feb 2018 14:12:09 +0000 (15:12 +0100)
committerMats Vernersson <mats.vernersson@smhi.se>
Wed, 28 Feb 2018 14:12:09 +0000 (15:12 +0100)
Possibility to add a max age limit to composite rules, to avoid that old data triggers (re-)generation of composites. By default the feature is disabled. The limit is disabled by setting the value -1.

13 files changed:
etc/create_db.sql
etc/upgrade_db.sql
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-createcomposite.xls
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJob.xls
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJobRemoved.xls
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-updatecomposite.xls
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest.java
itest/eu/baltrad/beast/router/BltRouterCompositeDBITest.xls
src/eu/baltrad/beast/rules/composite/CompositingRule.java
src/eu/baltrad/beast/rules/composite/CompositingRuleManager.java
src/eu/baltrad/beast/rules/site2d/Site2DRule.java
test/eu/baltrad/beast/rules/composite/CompositingRuleManagerTest.java
test/eu/baltrad/beast/rules/composite/CompositingRuleTest.java

index ea320aa..dc44df2 100644 (file)
@@ -77,7 +77,8 @@ create table beast_composite_rules (
   qitotal_field text,
   quantity text,
   qc_mode integer NOT NULL,
-  reprocess_quality boolean NOT NULL
+  reprocess_quality boolean NOT NULL,
+  max_age_limit integer NOT NULL
 );
 
 create table beast_composite_sources (
index f43418b..be64a23 100644 (file)
@@ -346,6 +346,17 @@ BEGIN
     ALTER TABLE beast_wrwp_rules ALTER COLUMN fields SET NOT NULL;
   END IF; 
 END;
+
+
+CREATE OR REPLACE FUNCTION update_beast_composite_rules_with_max_age_limit() RETURNS VOID AS $$
+BEGIN
+  PERFORM true FROM information_schema.columns WHERE table_name = 'beast_composite_rules' AND column_name = 'max_age_limit';
+  IF NOT FOUND THEN
+    ALTER TABLE beast_composite_rules ADD COLUMN max_age_limit integer;
+    UPDATE beast_composite_rules SET max_age_limit=-1;
+    ALTER TABLE beast_composite_rules ALTER COLUMN max_age_limit SET NOT NULL;
+  END IF;
+END;
 $$ LANGUAGE plpgsql;
 
 select create_beast_gmap_rules();
@@ -369,6 +380,7 @@ select update_beast_composite_rules_with_qc_mode();
 select update_beast_site2d_rules_with_qc_mode();
 select update_beast_composite_rules_with_reprocess_quality();
 select update_beast_wrwp_with_fields();
+select update_beast_composite_rules_with_max_age_limit();
 
 drop function create_beast_gmap_rules();
 drop function create_beast_host_filter();
@@ -391,3 +403,4 @@ drop function update_beast_composite_rules_with_qc_mode();
 drop function update_beast_site2d_rules_with_qc_mode();
 drop function update_beast_composite_rules_with_reprocess_quality();
 drop function update_beast_wrwp_with_fields()
+drop function update_beast_composite_rules_with_max_age_limit();
index 02c6795..af0a3c8 100644 (file)
Binary files a/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-createcomposite.xls and b/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-createcomposite.xls differ
index cccc04a..1b30116 100644 (file)
Binary files a/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJob.xls and b/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJob.xls differ
index 905106e..71ea60a 100644 (file)
Binary files a/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJobRemoved.xls and b/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-removeCompositeWithScheduledJobRemoved.xls differ
index a982aed..5fdcc7f 100644 (file)
Binary files a/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-updatecomposite.xls and b/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest-updatecomposite.xls differ
index ae7ea64..0828f56 100644 (file)
@@ -119,6 +119,7 @@ public class BltRouterCompositeDBITest extends TestCase {
     assertEquals(CompositingRule.SelectionMethod_NEAREST_RADAR, ((CompositingRule)def.getRule()).getSelectionMethod());
     assertEquals(CompositingRule.PPI, ((CompositingRule)def.getRule()).getMethod());
     assertEquals("0.5", ((CompositingRule)def.getRule()).getProdpar());
+    assertEquals(-1, ((CompositingRule)def.getRule()).getMaxAgeLimit());
     assertEquals(false, ((CompositingRule)def.getRule()).isApplyGRA());
     assertEquals(100.0, ((CompositingRule)def.getRule()).getZR_A(), 4);
     assertEquals(1.5, ((CompositingRule)def.getRule()).getZR_b(), 4);
@@ -151,6 +152,7 @@ public class BltRouterCompositeDBITest extends TestCase {
     ((CompositingRule)def.getRule()).setSelectionMethod(CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL);
     ((CompositingRule)def.getRule()).setMethod(CompositingRule.CAPPI);
     ((CompositingRule)def.getRule()).setProdpar("500.0");
+    ((CompositingRule)def.getRule()).setMaxAgeLimit(60);
     ((CompositingRule)def.getRule()).setApplyGRA(true);
     ((CompositingRule)def.getRule()).setZR_A(200.0);
     ((CompositingRule)def.getRule()).setZR_b(1.6);
@@ -212,6 +214,7 @@ public class BltRouterCompositeDBITest extends TestCase {
     rule.setSelectionMethod(CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL);
     rule.setMethod(CompositingRule.CAPPI);
     rule.setProdpar("500.0");
+    rule.setMaxAgeLimit(30);
     rule.setApplyGRA(true);
     rule.setZR_A(210.0);
     rule.setZR_b(1.7);
index 26ab37b..c3d17ea 100644 (file)
Binary files a/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest.xls and b/itest/eu/baltrad/beast/router/BltRouterCompositeDBITest.xls differ
index af12eb8..89df281 100644 (file)
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
@@ -35,6 +36,7 @@ import eu.baltrad.bdb.oh5.Metadata;
 import eu.baltrad.bdb.util.Date;
 import eu.baltrad.bdb.util.DateTime;
 import eu.baltrad.bdb.util.Time;
+import eu.baltrad.bdb.util.TimeDelta;
 import eu.baltrad.beast.db.Catalog;
 import eu.baltrad.beast.db.CatalogEntry;
 import eu.baltrad.beast.db.IFilter;
@@ -223,6 +225,13 @@ public class CompositingRule implements IRule, ITimeoutRule, InitializingBean {
   private String quantity = "DBZH";
   
   /**
+   * Incoming data exceeding this age threshold will not be handled 
+   * by this rule. In minutes. -1 indicates that no max age limit 
+   * will be applied.
+   */
+  private int maxAgeLimit = -1;
+  
+  /**
    * How the quality controls should be handled and used
    */
   private int qualityControlMode = QualityControlMode_ANALYZE_AND_APPLY;
@@ -415,15 +424,20 @@ public class CompositingRule implements IRule, ITimeoutRule, InitializingBean {
       IBltMessage generatedMessage = null;
       if (message instanceof BltDataMessage) {
         FileEntry file = ((BltDataMessage)message).getFileEntry();
-        CompositingRuleFilter ruleFilter = createFilter(getNominalTimeFromFile(file));
-        if (ruleFilter.fileMatches(file)) {
+        DateTime fileDateTime = getDateTimeFromFile(file);
+        UUID fileUuid = file.getUuid();
+        CompositingRuleFilter ruleFilter = createFilter(ruleUtil.createNominalTime(fileDateTime, getInterval()));
+        if (dateTimeExceedsMaxAgeLimit(fileDateTime)) {
+          logger.debug("CompositingRule - datetime in file " + fileUuid + " exceeds the maximum age limit of " + 
+              getMaxAgeLimit() + " in rule. File not handled by rule.");
+        } else if (ruleFilter.fileMatches(file)) {
           logger.info("ENTER: execute CompositingRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
-              ", file: " + file.getUuid());
+              ", file: " + fileUuid);
           
           generatedMessage = createComposite(message, ruleFilter);
           
           logger.info("EXIT: execute CompositingRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
-              ", file: " + file.getUuid()); 
+              ", file: " + fileUuid); 
         }
       }
       return generatedMessage;
@@ -432,6 +446,21 @@ public class CompositingRule implements IRule, ITimeoutRule, InitializingBean {
     }
   }
   
+  protected boolean dateTimeExceedsMaxAgeLimit(DateTime dateTime) {
+    if (getMaxAgeLimit() == -1) {
+      // -1 means disabled
+      return false;
+    }
+    
+    DateTime now = ruleUtil.nowDT();
+    
+    int secondsOffsetToLimit = -(60 * getMaxAgeLimit());
+    TimeDelta timeDeltaToLimit = new TimeDelta().addSeconds(secondsOffsetToLimit);
+    DateTime dateTimeLimit = now.add(timeDeltaToLimit);
+    
+    return dateTimeLimit.isAfter(dateTime);
+  }
+  
   protected IBltMessage createComposite(IBltMessage message, CompositingRuleFilter ruleFilter) {
     IBltMessage result = null;
 
@@ -529,6 +558,13 @@ public class CompositingRule implements IRule, ITimeoutRule, InitializingBean {
     return result;
   }
   
+  protected DateTime getDateTimeFromFile(FileEntry file) {
+    Metadata metaData = file.getMetadata();
+    Time time = metaData.getWhatTime();
+    Date date = metaData.getWhatDate();
+    return new DateTime(date, time);
+  }
+  
   protected DateTime getNominalTimeFromFile(FileEntry file) {
     Metadata metaData = file.getMetadata();
     Time t = metaData.getWhatTime();
@@ -833,6 +869,20 @@ public class CompositingRule implements IRule, ITimeoutRule, InitializingBean {
     this.quantity = quantity;
   }
 
+  /**
+   * @return the maximum age limit in minutes
+   */
+  public int getMaxAgeLimit() {
+    return maxAgeLimit;
+  }
+
+  /**
+   * @param maxAgeLimit the maximum age limit in minutes
+   */
+  public void setMaxAgeLimit(int maxAgeLimit) {
+    this.maxAgeLimit = maxAgeLimit;
+  }
+
   public boolean isNominalTimeout() {
     return nominalTimeout;
   }
index 3aa713e..e636dc9 100644 (file)
@@ -131,6 +131,7 @@ public class CompositingRuleManager implements IRuleManager {
     int selection_method = crule.getSelectionMethod();
     String method = crule.getMethod();
     String prodpar = crule.getProdpar();
+    int maxAgeLimit = crule.getMaxAgeLimit();
     boolean applygra = crule.isApplyGRA();
     double ZR_A = crule.getZR_A();
     double ZR_b = crule.getZR_b();
@@ -143,8 +144,8 @@ public class CompositingRuleManager implements IRuleManager {
     boolean reprocess_quality = crule.isReprocessQuality();
     
     template.update(
-        "insert into beast_composite_rules (rule_id, area, interval, timeout, byscan, selection_method, method, prodpar, applygra, ZR_A, ZR_b, ignore_malfunc, ctfilter, qitotal_field, quantity, nominal_timeout, qc_mode, reprocess_quality)"+
-        " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", new Object[]{ruleId, area, interval, timeout, byscan, selection_method, method, prodpar, applygra, ZR_A, ZR_b, ignoreMalfunc, ctfilter, qitotalField, quantity, nominal_timeout, qualityControlMode, reprocess_quality});
+        "insert into beast_composite_rules (rule_id, area, interval, timeout, byscan, selection_method, method, prodpar, max_age_limit, applygra, ZR_A, ZR_b, ignore_malfunc, ctfilter, qitotal_field, quantity, nominal_timeout, qc_mode, reprocess_quality)"+
+        " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", new Object[]{ruleId, area, interval, timeout, byscan, selection_method, method, prodpar, maxAgeLimit, applygra, ZR_A, ZR_b, ignoreMalfunc, ctfilter, qitotalField, quantity, nominal_timeout, qualityControlMode, reprocess_quality});
     storeSources(ruleId, crule.getSources());
     storeDetectors(ruleId, crule.getDetectors());
     storeFilter(ruleId, crule.getFilter());
@@ -158,8 +159,8 @@ public class CompositingRuleManager implements IRuleManager {
   public void update(int ruleId, IRule rule) {
     CompositingRule crule = (CompositingRule)rule;
     template.update(
-        "update beast_composite_rules set area=?, interval=?, timeout=?, byscan=?, selection_method=?, method=?, prodpar=?, applygra=?, ZR_A=?, ZR_b=?, ignore_malfunc=?, ctfilter=?, qitotal_field=?, quantity=?, nominal_timeout=?, qc_mode=?, reprocess_quality=? where rule_id=?",
-        new Object[]{crule.getArea(), crule.getInterval(), crule.getTimeout(), crule.isScanBased(), crule.getSelectionMethod(), crule.getMethod(), crule.getProdpar(), crule.isApplyGRA(), crule.getZR_A(), crule.getZR_b(), crule.isIgnoreMalfunc(), crule.isCtFilter(), crule.getQitotalField(), crule.getQuantity(), crule.isNominalTimeout(), crule.getQualityControlMode(), crule.isReprocessQuality(), ruleId});
+        "update beast_composite_rules set area=?, interval=?, timeout=?, byscan=?, selection_method=?, method=?, prodpar=?, max_age_limit=?, applygra=?, ZR_A=?, ZR_b=?, ignore_malfunc=?, ctfilter=?, qitotal_field=?, quantity=?, nominal_timeout=?, qc_mode=?, reprocess_quality=? where rule_id=?",
+        new Object[]{crule.getArea(), crule.getInterval(), crule.getTimeout(), crule.isScanBased(), crule.getSelectionMethod(), crule.getMethod(), crule.getProdpar(), crule.getMaxAgeLimit(), crule.isApplyGRA(), crule.getZR_A(), crule.getZR_b(), crule.isIgnoreMalfunc(), crule.isCtFilter(), crule.getQitotalField(), crule.getQuantity(), crule.isNominalTimeout(), crule.getQualityControlMode(), crule.isReprocessQuality(), ruleId});
     storeSources(ruleId, crule.getSources());
     storeDetectors(ruleId, crule.getDetectors());
     storeFilter(ruleId, crule.getFilter());
@@ -270,6 +271,7 @@ public class CompositingRuleManager implements IRuleManager {
         result.setSelectionMethod(rs.getInt("selection_method"));
         result.setMethod(rs.getString("method"));
         result.setProdpar(rs.getString("prodpar"));
+        result.setMaxAgeLimit(rs.getInt("max_age_limit"));
         result.setApplyGRA(rs.getBoolean("applygra"));
         result.setZR_A(rs.getDouble("ZR_A"));
         result.setZR_b(rs.getDouble("ZR_b"));
index 4b6aa21..8c58cd9 100644 (file)
@@ -245,7 +245,7 @@ public class Site2DRule implements IRule, InitializingBean {
     BltGenerateMessage generatedMessage = null;
     if (message instanceof BltDataMessage) {
       FileEntry file = ((BltDataMessage)message).getFileEntry();
-      logger.info("ENTER: execute ScansunRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
+      logger.info("ENTER: execute Site2DRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
           ", file: " + file.getUuid());
       
       if (fileMatchesRule(file)) {
@@ -254,7 +254,7 @@ public class Site2DRule implements IRule, InitializingBean {
         generatedMessage = createMessage(file.getUuid().toString(), date, time);
       }
       
-      logger.info("EXIT: execute ScansunRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
+      logger.info("EXIT: execute Site2DRule with ruleId: " + getRuleId() + ", thread: " + Thread.currentThread().getName() + 
           ", file: " + file.getUuid());
       
     }
index 98a05fa..277b5e7 100644 (file)
@@ -150,6 +150,7 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     rule.setSelectionMethod(CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL);
     rule.setMethod(CompositingRule.PPI);
     rule.setProdpar("0.5");
+    rule.setMaxAgeLimit(1440);
     rule.setApplyGRA(true);
     rule.setZR_A(10.0);
     rule.setZR_b(5.0);
@@ -162,8 +163,8 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     rule.setReprocessQuality(false);
     
     expect(jdbc.update(
-        "insert into beast_composite_rules (rule_id, area, interval, timeout, byscan, selection_method, method, prodpar, applygra, ZR_A, ZR_b, ignore_malfunc, ctfilter, qitotal_field, quantity, nominal_timeout, qc_mode, reprocess_quality)"+
-        " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", new Object[]{13, "seang", 12, 20, true, CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL, CompositingRule.PPI, "0.5", true, 10.0, 5.0, true, true, "se.baltrad.something", "VRAD", true, CompositingRule.QualityControlMode_ANALYZE, false}))
+        "insert into beast_composite_rules (rule_id, area, interval, timeout, byscan, selection_method, method, prodpar, max_age_limit, applygra, ZR_A, ZR_b, ignore_malfunc, ctfilter, qitotal_field, quantity, nominal_timeout, qc_mode, reprocess_quality)"+
+        " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", new Object[]{13, "seang", 12, 20, true, CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL, CompositingRule.PPI, "0.5", 1440, true, 10.0, 5.0, true, true, "se.baltrad.something", "VRAD", true, CompositingRule.QualityControlMode_ANALYZE, false}))
           .andReturn(0);
     
     methods.storeSources(13, sources);
@@ -205,6 +206,7 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     rule.setDetectors(detectors);
     rule.setMethod(CompositingRule.PPI);
     rule.setProdpar("0.5");
+    rule.setMaxAgeLimit(60);
     rule.setApplyGRA(true);
     rule.setZR_A(10.0);
     rule.setZR_b(5.0);
@@ -216,8 +218,8 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     rule.setQualityControlMode(CompositingRule.QualityControlMode_ANALYZE);
     rule.setReprocessQuality(true);
     
-    expect(jdbc.update("update beast_composite_rules set area=?, interval=?, timeout=?, byscan=?, selection_method=?, method=?, prodpar=?, applygra=?, ZR_A=?, ZR_b=?, ignore_malfunc=?, ctfilter=?, qitotal_field=?, quantity=?, nominal_timeout=?, qc_mode=?, reprocess_quality=? where rule_id=?",
-        new Object[]{"seang", 12, 20, true, CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL, CompositingRule.PPI, "0.5", true, 10.0, 5.0, true, true, "se.baltrad.something", "NOOP", true, CompositingRule.QualityControlMode_ANALYZE, true, 13}))
+    expect(jdbc.update("update beast_composite_rules set area=?, interval=?, timeout=?, byscan=?, selection_method=?, method=?, prodpar=?, max_age_limit=?, applygra=?, ZR_A=?, ZR_b=?, ignore_malfunc=?, ctfilter=?, qitotal_field=?, quantity=?, nominal_timeout=?, qc_mode=?, reprocess_quality=? where rule_id=?",
+        new Object[]{"seang", 12, 20, true, CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL, CompositingRule.PPI, "0.5", 60, true, 10.0, 5.0, true, true, "se.baltrad.something", "NOOP", true, CompositingRule.QualityControlMode_ANALYZE, true, 13}))
         .andReturn(0);
     
     methods.storeSources(13, sources);
@@ -390,6 +392,7 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     expect(rs.getInt("selection_method")).andReturn(CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL);
     expect(rs.getString("method")).andReturn(CompositingRule.PPI);
     expect(rs.getString("prodpar")).andReturn("0.5");
+    expect(rs.getInt("max_age_limit")).andReturn(30);
     expect(rs.getBoolean("applygra")).andReturn(true);
     expect(rs.getDouble("ZR_A")).andReturn(10.0);
     expect(rs.getDouble("ZR_b")).andReturn(5.0);
@@ -432,6 +435,7 @@ public class CompositingRuleManagerTest extends EasyMockSupport {
     assertEquals(CompositingRule.SelectionMethod_HEIGHT_ABOVE_SEALEVEL, result.getSelectionMethod());
     assertEquals(CompositingRule.PPI, result.getMethod());
     assertEquals("0.5", result.getProdpar());
+    assertEquals(30, result.getMaxAgeLimit());
     assertEquals(true, result.isApplyGRA());
     assertEquals(10.0, result.getZR_A(), 4);
     assertEquals(5.0, result.getZR_b(), 4);
index d54e2b0..c2f1f54 100644 (file)
@@ -29,6 +29,8 @@ import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -74,6 +76,8 @@ public class CompositingRuleTest extends EasyMockSupport {
     public IBltMessage createMessage(DateTime nominalTime, Map<String, CatalogEntry> entries);
     public IBltMessage createComposite(IBltMessage message, CompositingRuleFilter ruleFilter);
     public DateTime getNominalTimeFromFile(FileEntry file);
+    public DateTime getDateTimeFromFile(FileEntry file);
+    public boolean dateTimeExceedsMaxAgeLimit(DateTime dateTime);
   };
 
   @Before
@@ -146,6 +150,61 @@ public class CompositingRuleTest extends EasyMockSupport {
     classUnderTest.setZR_b(10.0);
     assertEquals(10.0, classUnderTest.getZR_b(), 4);
   }
+  
+  @Test
+  public void testExceedsMaxAgeLimit_disabled() {
+    int disabledIndicator = -1;
+    classUnderTest.setMaxAgeLimit(disabledIndicator);
+    
+    Calendar fileDate = GregorianCalendar.getInstance();
+    fileDate.add(Calendar.MINUTE, -1000);
+    
+    DateTime fileDateTime = classUnderTest.getRuleUtilities().createDateTime(fileDate.getTime());
+    
+    boolean dateTimeExceedsLimit = classUnderTest.dateTimeExceedsMaxAgeLimit(fileDateTime);
+    
+    assertEquals(false, dateTimeExceedsLimit);
+  }
+  
+  @Test
+  public void testExceedsMaxAgeLimit_exceeding() {
+    int maxAgeLimitMinutes = 60;
+    classUnderTest.setMaxAgeLimit(maxAgeLimitMinutes);
+    
+    Calendar fileDate = GregorianCalendar.getInstance();
+    fileDate.add(Calendar.MINUTE, -(maxAgeLimitMinutes + 1)); // exceeding limit with 1 minute
+    
+    DateTime fileDateTime = new DateTime(2018, 2, 26, 5, 14, 30);
+    DateTime nowDateTime = new DateTime(2018, 2, 26, 6, 15, 00); // 60 minutes and 30 seconds after file time
+    
+    expect(ruleUtil.nowDT()).andReturn(nowDateTime);
+    
+    replayAll();
+    
+    boolean dateTimeExceedsLimit = classUnderTest.dateTimeExceedsMaxAgeLimit(fileDateTime);
+    
+    assertEquals(true, dateTimeExceedsLimit);
+  }
+  
+  @Test
+  public void testExceedsMaxAgeLimit_notExceeding() {
+    int maxAgeLimitMinutes = 60;
+    classUnderTest.setMaxAgeLimit(maxAgeLimitMinutes);
+    
+    Calendar fileDate = GregorianCalendar.getInstance();
+    fileDate.add(Calendar.MINUTE, -(maxAgeLimitMinutes + 1)); // exceeding limit with 1 minute
+    
+    DateTime fileDateTime = new DateTime(2018, 2, 26, 5, 14, 30);
+    DateTime nowDateTime = new DateTime(2018, 2, 26, 6, 14, 00); // 59 minutes and 30 seconds after file time
+    
+    expect(ruleUtil.nowDT()).andReturn(nowDateTime);
+    
+    replayAll();
+    
+    boolean dateTimeExceedsLimit = classUnderTest.dateTimeExceedsMaxAgeLimit(fileDateTime);
+    
+    assertEquals(false, dateTimeExceedsLimit);
+  }
 
   @Test
   public void testTimeout() throws Exception {
@@ -1075,19 +1134,25 @@ public class CompositingRuleTest extends EasyMockSupport {
   
   @Test
   public void testHandle_fileMatches() throws Exception {
-    testHandle(true); 
+    testHandle(true, false); 
   }
   
   @Test
   public void testHandle_fileDoesNotMatch() throws Exception {
-    testHandle(false); 
+    testHandle(false, false); 
   }
   
-  private void testHandle(boolean fileMatches) {
+  @Test
+  public void testHandle_exceedsMaxAgeLimit() throws Exception {
+    testHandle(true, true); 
+  }
+  
+  private void testHandle(boolean fileMatches, boolean exceedsMaxAgeLimit) {
     final ICompositingMethods methods = createMock(ICompositingMethods.class);
 
     FileEntry fileEntry = createMock(FileEntry.class);
     DateTime dateTime = new DateTime(2017, 02, 01, 15, 10, 0);
+    DateTime nominalDateTime = new DateTime(2017, 02, 01, 15, 0, 0);
     CompositingRuleFilter filter = createMock(CompositingRuleFilter.class);
     
     BltDataMessage msg = new BltDataMessage();
@@ -1104,18 +1169,29 @@ public class CompositingRuleTest extends EasyMockSupport {
         return methods.createFilter(nominalTime);
       }
       
-      protected DateTime getNominalTimeFromFile(FileEntry file) {
-        return methods.getNominalTimeFromFile(file);
+      protected DateTime getDateTimeFromFile(FileEntry file) {
+        return methods.getDateTimeFromFile(file);
+      }
+      
+      protected boolean dateTimeExceedsMaxAgeLimit(DateTime dateTime) {
+        return methods.dateTimeExceedsMaxAgeLimit(dateTime);
       }
     };
+    
+    classUnderTest.setRuleUtilities(ruleUtil);
 
-    expect(methods.getNominalTimeFromFile(fileEntry)).andReturn(dateTime);
-    expect(methods.createFilter(dateTime)).andReturn(filter);
-    expect(filter.fileMatches(fileEntry)).andReturn(fileMatches);
+    expect(fileEntry.getUuid()).andReturn(new UUID(100, 100)).anyTimes();      
+
+    expect(methods.getDateTimeFromFile(fileEntry)).andReturn(dateTime);
+    expect(methods.dateTimeExceedsMaxAgeLimit(dateTime)).andReturn(exceedsMaxAgeLimit);
+    expect(ruleUtil.createNominalTime(dateTime, classUnderTest.getInterval())).andReturn(nominalDateTime);
+    expect(methods.createFilter(nominalDateTime)).andReturn(filter);
+    if (!exceedsMaxAgeLimit) {
+      expect(filter.fileMatches(fileEntry)).andReturn(fileMatches);      
+    }
     
-    if (fileMatches) {
+    if (fileMatches && !exceedsMaxAgeLimit) {
       expect(methods.createComposite(msg, filter)).andReturn(genMsg);
-      expect(fileEntry.getUuid()).andReturn(new UUID(100, 100)).anyTimes();      
     }
     
     replayAll();
@@ -1123,7 +1199,7 @@ public class CompositingRuleTest extends EasyMockSupport {
     IBltMessage result = classUnderTest.handle(msg);
     
     verifyAll();
-    if (fileMatches) {
+    if (fileMatches && !exceedsMaxAgeLimit) {
       assertSame(result, genMsg);      
     } else {
       assertNull(result);