Skip to content

Proxy

Proxy is one of the structural patterns that, in the context of creation, has a similar structure to the Decorator. This pattern adds a layer between the interface and the specific implementation. This layer, depending on the type of proxy we implement, may fulfill a different function.

proxy

We can identify several types of proxies. Some of them are:

  • remote, the purpose of which is to represent an object located in another space (e.g. another JVM).
  • virtual, which can be used as a cache for an object reference.
  • security, which adds a layer of security to the object.

A proxy object should implement the same interface as the object it wraps. Very often, the end user of the interface does not know that he uses a proxy, which also means that the proxy can hide some implementation details.

The following example shows a simple use of a security proxy.

The example consists of the following classes:

  • Message - represents the sent message
  • SessionTokens - stores user tokens
  • TokenValidator - checks if the user token is valid
  • MessageSender - is a common interface on which a "real" object and a proxy are based
  • SlackMessageSender - is an implementation of MessageSender
  • SlackMessageSenderProxy, which is an middleman and introduces a simple security layer - the user can send a message if he has the right token.
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class Message {
  private String channelName;
  private LocalDateTime postDate;
  private String author;
  private String text;

  public Message(final String channelName, final String author, final String text) {
    this.channelName = channelName;
    this.author = author;
    this.text = text;
    this.postDate = LocalDateTime.now();
  }
}
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public class SessionTokens {
  private final Map<String, UUID> userNameToSessionToken = new HashMap<>();

  public Optional<UUID> getUserToken(final String userName) {
    return Optional.ofNullable(userNameToSessionToken.getOrDefault(userName, null));
  }

  public void createTokenForUser(final String userName) {
    userNameToSessionToken.put(userName, UUID.randomUUID());
  }
}
import java.util.Random;
import java.util.UUID;

public class TokenValidator {
  boolean isExpired(final UUID token) {
    return new Random().nextBoolean(); // for example purposes
  }
}
public interface MessageSender {
  void sendMessage(String channelName, String username, String message);
}
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class SlackMessageSender implements MessageSender {

  private final List<Message> messages = new ArrayList<>();

  @Override
  public void sendMessage(final String channelName, final String username, final String messageText) {
    final Message message = new Message(channelName, username, messageText);
    log.info("Sent message {}", message);
    messages.add(message);
    log.info("Messages: {}", messages);
  }
}
import lombok.extern.slf4j.Slf4j;

import java.util.Optional;
import java.util.UUID;

@Slf4j
public class SlackMessageSenderProxy implements MessageSender {

  private final MessageSender messageSender;
  private final SessionTokens sessionTokens;
  private final TokenValidator tokenValidator;

  public SlackMessageSenderProxy(final MessageSender messageSender, final SessionTokens sessionTokens, final TokenValidator tokenValidator) {
    this.messageSender = messageSender;
    this.sessionTokens = sessionTokens;
    this.tokenValidator = tokenValidator;
  }

  @Override
  public void sendMessage(final String channelName, final String username, final String message) {
    final Optional<UUID> userTokenOptional = sessionTokens.getUserToken(username);
    if (userTokenOptional.isPresent()) {
      final UUID existingToken = userTokenOptional.get();
      if (!tokenValidator.isExpired(existingToken)) {
        messageSender.sendMessage(channelName, username, message);
      }
    } else {
      log.info("Message from {} not sent to channel {} because user has no valid access token", username, channelName);
    }
  }
}

Sample use of the pattern - the message will be sent only if the generated token is valid.

public class SecurityProxyDemo {
  public static void main(String[] args) {
    TokenValidator tokenValidator = new TokenValidator();
    SessionTokens sessionTokens = new SessionTokens();
    String userName = "Andrzej";
    String channelName = "design_patterns";
    String message = "I will learn what proxy is!";
    sessionTokens.createTokenForUser(userName);

    MessageSender realMessageSender = new SlackMessageSender();
    MessageSender proxy = new SlackMessageSenderProxy(realMessageSender, sessionTokens, tokenValidator);

    proxy.sendMessage(channelName, userName, message);
  }
}