Skip to content

Chain of responsibility

The Chain of responsibility is a pattern that describes how to create a chain of objects that are able to handle a specific request. The request is usually sent by the client and then goes to the first element of the chain. If the first element is able to properly handle the request then the chain processing ends. The next handlers of the chain will have no "contact" with the request. On the other hand, if an element of the chain is unable to handle the request, then - unless it is the last element of the chain - it tries to shift the responsibility of handling it to the next element.

Construction

In order to create the chain described above, we need the following elements:

  • a common interface that represents the handler that at a given element of the chain may attempt to service the request
  • different implementations of handlers, usually their number implies the number of elements in the chain
  • a class representing an element of the chain, i.e. one using a specific handler implementation and pointing to the next element.


cor

Example

The example below shows the use of the Chain of responsibility pattern. The sent request is an attempt to log in by the user. We have three authentication methods:

  • with a username and password
  • using the so-called Bearer token
  • using the AWS token

All three implementations are simplified for the example.

The implementation consists of the following elements:

  • The Credentials interface representing the login data. It has three implementations:
    • AwsSignature
    • BearerToken
    • UsernameAndPasswordCredentials
  • The AuthenticationHandler interface representing the common handler interface. It has 3 implementations:
    • AwsAuthenticationHandler
    • BearerTokenAuthenticationHandler
    • UsernameAndPasswordAuthenticationHandler
  • The ChainAuthenticationElement class representing the element of the chain. It stores information about the available handler, the next element, and has authentication logic.
  • The ChainOfResponsibilityDemo class builds a chain from three elements, then tries to log in with available Credential objects (i.e., uses the created chain).
public interface Credentials {
  String getCredentials(String userId);
}
import java.util.UUID;

public class AwsSignature implements Credentials {
  @Override
  public String getCredentials(final String userId) {
    return UUID.randomUUID().toString(); // dummy implementation
  }
}
public class BearerToken implements Credentials {
  @Override
  public String getCredentials(final String userId) {
    return "1/mZ1edKKACtPAb7zGlwSzvs72PvhAbGmB8K1ZrGxpcNM"; // dummy implementation
  }
}
public class UsernameAndPasswordCredentials implements Credentials {

  // dummy implementation -> load real user UnP in real implementation
  @Override
  public String getCredentials(final String userId) {
    return "andrzej:Andrzej_123";
  }
}
public interface AuthenticationHandler {
  boolean authenticate(Credentials credentials);
  boolean supports(Class<?> clazz);
}
public class AwsAuthenticationHandler implements AuthenticationHandler {
  @Override
  public boolean authenticate(final Credentials credentials) {
    if (supports(credentials.getClass())) {
      return authenticateInAws(credentials);
    }
    return false;
  }

  @Override
  public boolean supports(final Class<?> clazz) {
    return clazz.equals(AwsSignature.class);
  }

  public boolean authenticateInAws(Credentials credentials) {
    return credentials.getCredentials("someUserId").length() == 5; // dummy implementation
  }
}
import java.util.Random;

public class BearerTokenAuthenticationHandler implements AuthenticationHandler {

  @Override
  public boolean authenticate(final Credentials credentials) {
    if (supports(credentials.getClass())) {
      return isTokenValid(credentials);
    }
    return false;
  }

  @Override
  public boolean supports(final Class<?> clazz) {
    return clazz.equals(BearerToken.class);
  }

  public boolean isTokenValid(final Credentials credentials) {
    return (new Random().nextInt(3) % 3) != 0; // dummy implementation
  }
}
import java.util.Random;

public class UsernameAndPasswordAuthenticationHandler implements AuthenticationHandler {
  @Override
  public boolean authenticate(final Credentials credentials) {
    if (supports(credentials.getClass())) {
      return isPasswordValid(credentials);
    }
    return false;
  }

  @Override
  public boolean supports(final Class<?> clazz) {
    return clazz.isInstance(UsernameAndPasswordCredentials.class);
  }

  public boolean isPasswordValid(Credentials credentials) {
    return new Random().nextBoolean(); // dummy implementation - use real credentials in the real implementation
  }
}
import lombok.extern.slf4j.Slf4j;

import static java.util.Objects.nonNull;

@Slf4j
public class ChainAuthenticationElement {

  private final AuthenticationHandler authenticationHandler;
  private final ChainAuthenticationElement next;

  public ChainAuthenticationElement(final AuthenticationHandler authenticationHandler, final ChainAuthenticationElement next) {
    this.authenticationHandler = authenticationHandler;
    this.next = next;
  }

  public ChainAuthenticationElement(final AuthenticationHandler authenticationHandler) {
    this.authenticationHandler = authenticationHandler;
    this.next = null;
  }

  public boolean authenticate(final Credentials credentials) {
    final boolean authenticated = authenticationHandler.authenticate(credentials);
    if (authenticated) {
      log.info("Authentication using handler " + authenticationHandler.getClass().getSimpleName());
      return true;
    }

    return nonNull(next) && next.authenticate(credentials);
  }
}
public class ChainOfResponsibilityDemo {
  public static void main(String[] args) {
    final AuthenticationHandler authenticationHandlerUnP = new UsernameAndPasswordAuthenticationHandler();
    final AuthenticationHandler authenticationHandlerBearer = new BearerTokenAuthenticationHandler();
    final AuthenticationHandler authenticationHandlerAws = new AwsAuthenticationHandler();

    final ChainAuthenticationElement lastElement = new ChainAuthenticationElement(authenticationHandlerAws);
    final ChainAuthenticationElement middleElement = new ChainAuthenticationElement(authenticationHandlerBearer, lastElement);
    final ChainAuthenticationElement firstElement = new ChainAuthenticationElement(authenticationHandlerUnP, middleElement);

    firstElement.authenticate(new AwsSignature());
    firstElement.authenticate(new UsernameAndPasswordCredentials());
    firstElement.authenticate(new BearerToken());

    // possible output: INFO pl.sdacademy.cor.ChainAuthenticationElement - Authentication using handler BearerTokenAuthenticationHandler
  }
}