0

I have a typical use case where i am consuming to a message broker. The messages are <^> delimited strings. I parse each and every message create POJO's and then apply different validations to understand if the message is useful for further processing. We call these validations as filters.

These filters are dynamically created by the users of the applications and these filters needs to be dynamically applied to the incoming messages.

As of now i am validating the messages using IF-ELSE loops. But, I would like to check if there are any design patterns which are already making this problem statement elegant.

A typical filter has a FilterCriteria which talks about the Conditions which needs to be checked against a message.

Code:

// load filters for a given party and apply on the rawSyslogMessage.
    private boolean applyFilter(RawSyslogMessage message) throws EntityNotFoundException {

        boolean isDropped = false;

        logger.info("---applyFilter()::rawSyslog :" + message);

        String partyID = message.getPartyID();

        // load all filters for the given party
        List<Filter> filters = filterService.getAll(partyID);

        if (filters != null) {
            for (Filter filter : filters) {

                FilterCriteria filterCriteria = filter.getFilterCriteria();
                String field = filterCriteria.getField();
                String condition = filterCriteria.getCondition();
                String action = filterCriteria.getAction();

                // FILTER. Consider applying all fitlers on a message.
                if (filter.getName().toUpperCase().equals("PRIORITY") && action.toUpperCase().equals("ALLOW")) {
                    if (condition.toUpperCase().equals("GREATER")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else {
                            logger.info("The message is sent for correlation");
                        }
                    } else if (condition.toUpperCase().equals("LESSER")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is sent for correlation");
                        } else {
                            logger.info("The message is dropped");
                            isDropped = true;
                        }
                    } else if (condition.toUpperCase().equals("EQUALS")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is sent for correlation");
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else {
                            logger.info("The message is dropped");
                            isDropped = true;
                        }
                    } else if (condition.toUpperCase().equals("BETWEEN")) {
                        String[] range = field.split("TO");
                        String _left = range[0];
                        String _right = range[1];
                        if (message.getSeverity() >= Integer.parseInt(_left)
                                && message.getSeverity() <= Integer.parseInt(_right)) {
                            logger.info("The message is sent for correlation");
                        } else {
                            logger.info("The message is dropped");
                            isDropped = true;
                        }
                    }
                } else if (filter.getName().toUpperCase().equals("PRIORITY")
                        && action.toUpperCase().equals("DISCARD")) {
                    if (condition.toUpperCase().equals("GREATER")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is sent for correlation");
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is sent for correlation");
                        } else {
                            logger.info("The message is dropped");
                            isDropped = true;
                        }
                    } else if (condition.toUpperCase().equals("LESSER")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is sent for correlation");
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else {
                            logger.info("The message is sent for correlation");
                        }
                    } else if (condition.toUpperCase().equals("EQUALS")) {
                        if (Long.toString(message.getSeverity()).equals(field)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else if (message.getSeverity() < Long.parseLong(field)) {
                            logger.info("The message is sent for correlation");
                        } else {
                            logger.info("The message is sent for correlation");
                        }
                    } else if (condition.toUpperCase().equals("BETWEEN")) {
                        String[] range = field.split("TO");
                        String _left = range[0];
                        String _right = range[1];
                        if (message.getSeverity() >= Integer.parseInt(_left)
                                && message.getSeverity() <= Integer.parseInt(_right)) {
                            logger.info("The message is dropped");
                            isDropped = true;
                        } else {
                            logger.info("The message is sent for correlation");
                        }
                    }
                }
            }
        }

        return isDropped;
    }

I have referred through the

https://en.wikipedia.org/wiki/Specification_pattern (as suggested in Style for control flow with validation checks)

https://en.wikipedia.org/wiki/Strategy_pattern

https://en.wikipedia.org/wiki/Command_pattern

But, none of these seem to be fitting my requirements. Please help.

Doc Brown
  • 206,877

4 Answers4

2

Take a look at Intercepting filter pattern, it might fit your purpose. It provides flexibility and extensibility for possibly creating new filter types, and you could use it to bind together the desired filter combinations for specific tasks.

ichantz
  • 368
1

Another approach to take is to use monads to hold the validation result. A good overview of the approach is provide in Functional Data Validation using monads and applicative functors. Basically, the monad object holds either the validated result, or a validation message that can logged, or used for more useful feedback.

For instance, using one of the Try monads for Java:

Try<String> validatedCondition = Try.apply( () -> validateCondition( condition ) );
Try<String> validatedField = Try.apply( () -> validateField( field ) );
...
if ( validatedFilterCriteria.isSuccess() &&
     validatedCondition.isSuccess() &&
     validatedField.isSuccess() ) {
  // now operate on the validated values
}
else {
  // a validation failed, so extract the failure message
  Try<String> result = validatedFilterCriteria
     .andThen( validatedCondition )
     .andThen( validatedField );
  log.error( "Validation failure: " + result.failureMessage() );
}

...
private String validateCondition( String condition ) {
  assert condition != null;
  assert !condition.isEqual("");
  ...
  if ( condition.isValid() ) {
     return condition;
  }
}
... // similarly for other validations

While Java is much more verbose than Scala, you can accomplish the same thing and make for more systematic validations.

BobDalgleish
  • 4,694
0

Well the most obvious improvement would be that instead of the Filter class just being a bag of properties, and your single long method doing the filtering bases on these properties, "Filter" would be an interface and you would have subclasses implementing it that would include the actual code for performing each type of filter.

I'm not sure exactly which design pattern this would be, probably the Strategy Pattern, which you already mention. What about that pattern makes you think it doesn't match your requirements?

0

A combination of the decorate and strategy patterns have been a very good solution for me when handling scanned questionnaires.

The validation is a strategy build from one or more strategies decorated around each other.

The user can then select each available strategy and the order of each strategy (ie. the order of how the strategies are decorated).

Bent
  • 2,576
  • 1
  • 14
  • 18