Protection Proxy by dynamic proxy

The Protection variation of the Proxy Pattern is meant to provide an extra level on flexibility, so that part of the interface of a class could be accessed by the user.

To show an usage of this pattern, we'll use the proxy support that Java makes available through the reflection package. It is called a dynamic proxy, Java creates it on the fly, when we need it. Since the proxy creation is done by Java directly, we should have a way of specifying the behavior we want to implements. This is done by making available to the Java proxy an object that instantiates a class derived from the interface InvocationHandler.

We want to implement a matchmaking system where a profile is available for each user. The issue is that there is one setter a user can't call for its own profile (the one to set how much a user likes that profile - you can't do that on your own profile!), but just on other people profile.

So, if this is the interface for the profile:
public interface PersonBean {
    String getName();
    String getGender();
    String getInterests();
    int getRating();

    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    void setRating(int rating);
}

And this is a concrete implementation:
public class PersonBeanImpl implements PersonBean {
    private String name;
    private String gender;
    private String interests;
    private int rating;
    private int count;

    public String getName() {
        return name;
    }

    public String getGender() {
        return gender;
    }

    public String getInterests() {
        return interests;
    }

    public int getRating() {
        if(count == 0)
            return 0;
        return rating / count;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setInterests(String interests) {
        this.interests = interests;
    }

    public void setRating(int rating) {
        this.rating += rating;
        ++count;
    }
}
We want to make available a proxy to protect the access to the setRating() method.

To do that, we create two class implementing InvocationHandler, one to be used from a user when it wants to access its own profile, and the other when it wants to access someone else's profile:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class IHOwner implements InvocationHandler {
    PersonBean person;

    public IHOwner(PersonBean person) {
        this.person = person;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
    throws IllegalAccessException {
        try {
            if(method.getName().startsWith("get"))
                return method.invoke(person, args);
            if(method.getName().equals("setRating"))
                throw new IllegalAccessException();
            if(method.getName().startsWith("set"))
                return method.invoke(person, args);
        } catch(InvocationTargetException ite) {
            ite.printStackTrace();
        }
        return null;
    }
}

// ...
public class IHNonOwner implements InvocationHandler {
    PersonBean person;

    public IHNonOwner(PersonBean person) {
        this.person = person;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
    throws IllegalAccessException {
        try {
            if(method.getName().startsWith("get"))
                return method.invoke(person, args);
            if(method.getName().equals("setRating"))
                return method.invoke(person, args);
            if(method.getName().startsWith("set"))
                throw new IllegalAccessException();
        } catch(InvocationTargetException ite) {
            ite.printStackTrace();
        }
        return null;
    }
}

Now, the job of creating the proxy is done by Java. We "just" have to call this piece of code:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public static PersonBean getProxy(PersonBean person, InvocationHandler ih) {
    return (PersonBean)Proxy.newProxyInstance(
        person.getClass().getClassLoader(),
        person.getClass().getInterfaces(), ih);
}

We call the factory method newProxyInstance() in the class Proxy, passing it the loader and the interfaces to the class the proxy has to stand for, and the required invocation handler that would tell the proxy how to behave.

Here is a little test client that should help understanding how to use this proxy:
PersonBean p = new PersonBeanImpl();
PersonBean proxy = PersonBeanProxy.getProxy(p, new IHOwner(p)); // 1
try {
    proxy.setRating(10);
} catch (Exception e) {
    System.out.println("Can't set rating.");
}

proxy = PersonBeanProxy.getProxy(p, new IHNonOwner(p)); // 2
try {
    proxy.setInterests("Snorkeling");
} catch (Exception e) {
    System.out.println("Can't set interests.");
}
1. We want the proxy acting as an owner, since we passed an IHOwner instance to the creator. That means it is not possible to call setRating() on it. If we try to call this method, we'll have an exception.
2. Now the proxy is acting as a non owner. So we can't call any setter that is not setRating(). Therefore, the call to setInterests() would result in an exception.

I have written this post while reading chapter eleven of Head First Design Patterns, that is all about proxy.

No comments:

Post a Comment