Tracking Exceptions With Spring – Part 2 – Delegate Pattern

In my last blog, I started to talk about the need to figure out whether or not your application is misbehaving in it’s production environment. I said that one method of monitoring your application is by checking its log files for exceptions and taking appropriate action if one is found. Obviously, log files can take up hundreds of megabytes of disk space and it’s impractical and really boring to monitor them by hand.

I also said that there were several ways of automatically monitoring log files and proposed a Spring based utility that combs log files daily and sends you an email if / when it finds any exceptions.

I only got as far as describing the first class: the FileLocator, which will search a directory and it’s sub-directories for log files. When it finds one, it passes it to the FileValidator.

The FileValidator has to perform several checks on the file. Firstly, it has to determine whether the file is young enough to examine for exceptions. The idea is that as the application runs periodically, there’s no point in checking all the files found in the directory for errors, we only want those files that have been created or updated since the application last ran.

The idea behind this design is to combine several implementations of the same interface, creating an aggregate object that’s responsible for validating files. The eagle-eyed reader will notice that this is an implementation of the Delegate Pattern.

Screen+Shot+2014-03-08+at+16.59.55

In the class diagram above instances of RegexValidator and FileAgeValidator are injected into the FileValidator and it delegates its validation tasks to these classes.

Taking each of these in turn and dealing with the Validator interface first…

public interface Validator { 

  /** The validation method */ 
  public <T> boolean validate(T obj); 

}

The code above demonstrates the simplicity of the Validator interface. It has a single method validate(T obj), which is a Generic Method call that increases the flexibility and re-usability of this interface. When classes implement this interface, they can change the input argument type to suit their own purposes… as demonstrated by the first implementation below:

public class RegexValidator implements Validator { 

  private static final Logger logger = LoggerFactory.getLogger(RegexValidator.class); 

  private final Pattern pattern; 

  public RegexValidator(String regex) { 
    pattern = Pattern.compile(regex); 
    logger.info("loaded regex: {}", regex); 
  } 

  @Override 
  public <T> boolean validate(T string) { 

    boolean retVal = false; 
    Matcher matcher = pattern.matcher((String) string); 
    retVal = matcher.matches(); 
    if (retVal && logger.isDebugEnabled()) { 
      logger.debug("Found error line: {}", string); 
    } 

    return retVal; 
  } 
}

The RegexValidator class has a single argument constructor that takes a Regular Expression string. This is then converted to a Pattern instance variable and is used by the validate(T string) method to test whether or not the String input argument matches original constructor arg regular expression. If it does, then validate(T string) will return true.

@Service 
public class FileAgeValidator implements Validator { 

  @Value("${max.days}") 
  private int maxDays; 

  /** 
   * Validate the age of the file. 
   * 
   * @see com.captaindebug.errortrack.Validator#validate(java.lang.Object) 
   */ 
  @Override 
  public <T> boolean validate(T obj) { 

    File file = (File) obj; 
    Calendar fileDate = getFileDate(file); 

    Calendar ageLimit = getFileAgeLimit(); 

    boolean retVal = false; 
    if (fileDate.after(ageLimit)) { 
      retVal = true; 
    } 

    return retVal; 
  } 

  private Calendar getFileAgeLimit() { 

    Calendar cal = Calendar.getInstance(); 
    cal.add(Calendar.DAY_OF_MONTH, -1 * maxDays); 
    return cal; 
  } 

  private Calendar getFileDate(File file) { 

    long fileDate = file.lastModified(); 
    Calendar when = Calendar.getInstance(); 
    when.setTimeInMillis(fileDate); 
    return when; 
  } 

}

The second Validator(T obj) implementation is the FileAgeValidator shown above and the first thing to note is that the whole thing is driven by the max.days property. This is injected into the FileAgeValidator’s @Value annotated maxDays instance variable. This variable determines the maximum age of the file in days. This the file is older than this value, then the validate(T obj) will return false.

In this implementation, the validate(T obj) ‘obj’ argument is cast to a File object, which is then used to convert the date of the file into a Calendar object. The next line of code converts the maxDays variable into a second Calendar object: ageLimit. The ageLimit is then compared with the fileDate object. If the fileDate is after the ageLimit then validate(T obj) returns true.

The final class in the validator package is the FileValidator, which as shown above delegates a lot of its responsibility to the other three other aggregated validators: one FileAgeValidator and two RegexValidator’s.

@Service 
public class FileValidator implements Validator { 

  private static final Logger logger = LoggerFactory.getLogger(FileValidator.class); 

  @Value("${following.lines}") 
  private Integer extraLineCount; 

  @Autowired 
  @Qualifier("scan-for") 
  private RegexValidator scanForValidator; 

  @Autowired(required = false) 
  @Qualifier("exclude") 
  private RegexValidator excludeValidator; 

  @Autowired 
  private FileAgeValidator fileAgeValidator; 

  @Autowired 
  private Results results; 

  @Override 
  public <T> boolean validate(T obj) { 

    boolean retVal = false; 
    File file = (File) obj; 
    if (fileAgeValidator.validate(file)) { 
      results.addFile(file.getPath()); 
      checkFile(file); 
      retVal = true; 
    } 
    return retVal; 
  } 

  private void checkFile(File file) { 

    try { 
      BufferedReader in = createBufferedReader(file); 
      readLines(in, file); 
      in.close(); 
    } catch (Exception e) { 
      logger.error("Error whilst processing file: " + file.getPath() + " Message: " + e.getMessage(), e); 
    } 
  } 

  @VisibleForTesting 
  BufferedReader createBufferedReader(File file) throws FileNotFoundException { 
    BufferedReader in = new BufferedReader(new FileReader(file)); 
    return in; 
  } 

  private void readLines(BufferedReader in, File file) throws IOException { 
    int lineNumber = 0; 
    String line; 
    do { 
      line = in.readLine(); 
      if (isNotNull(line)) { 
        processLine(line, file.getPath(), ++lineNumber, in); 
      } 
    } while (isNotNull(line)); 
  } 

  private boolean isNotNull(Object obj) { 
    return obj != null; 
  } 

  private int processLine(String line, String filePath, int lineNumber, BufferedReader in) throws IOException { 

    if (canValidateLine(line) && scanForValidator.validate(line)) { 
      List<String> lines = new ArrayList<String>(); 
      lines.add(line); 
      addExtraDetailLines(in, lines); 
      results.addResult(filePath, lineNumber, lines); 
      lineNumber += extraLineCount; 
    } 

    return lineNumber; 
  } 

  private boolean canValidateLine(String line) { 
    boolean retVal = true; 
    if (isNotNull(excludeValidator)) { 
      retVal = !excludeValidator.validate(line); 
    } 
    return retVal; 
  } 

  private void addExtraDetailLines(BufferedReader in, List<String> lines) throws IOException { 

    for (int i = 0; i < extraLineCount; i++) { 
      String line = in.readLine(); 
      if (isNotNull(line)) { 
        lines.add(line); 
      } else { 
        break; 
      } 
    } 
  } 

}

The FileValidator’s validate(T obj) takes a File as an argument. Its first responsibility is to validate the age of the file. If that validator returns true, then it informs the Report class that it’s found a new, valid file. It then checks the file for errors, adding any it finds to the Report instance. It does this by using a BufferedReader to check each line of the file in turn. Before checking whether a line contains an error, it checks that the line isn’t excluded from the check – i.e. that it doesn’t match the excluded exceptions or ones we’re not interested in. If the line doesn’t match the excluded exceptions, then it’s checked for exceptions that we need to find using the second instance of the RegexValidator. If the line does contain an error it’s added to a List<String> object. A number of following lines are then read from the file added to the list to make the report more readable.

And so, the file parsing continues, checking each line at a time looking for errors and building up a report, which can be processed later.

That cover’s validating files using Delegate Pattern adding any exceptions found to the Report, but how does this Report object work? I’ve not mentioned it, and how is the output generated? More on that next time.

 

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


seven − 5 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books