Proxy Pattern

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it.

Proxy Pattern Diagram

Implementation

Creating Subject interface:

import java.rmi.*;
 
/* We extend Remote here to define methods below as remote interface */
public interface GumballMachineRemote extends Remote {
	/* Serializable */
	public int getCount() throws RemoteException; /* support RemoteException */
	public String getLocation() throws RemoteException;
	/* Unserializable */
	public State getState() throws RemoteException;
}

Serializing State:

import java.io.*;
 
/* Making State Serializable */
public interface State extends Serializable {
	public void insertQuarter();
	public void ejectQuarter();
	public void turnCrank();
	public void dispense();
}

Reworking States:

public class NoQuarterState implements State {
	/* Serialized class version identifier. Java uses it during deserialization
	to check that the sender's and receiver's class definitions are compatible. */
	private static final long serialVersionUID = 2L;
	/* transient excludes this field from serialization.
	After deserialization on the monitor side, this field is null. */
	transient GumballMachine gumballMachine;
	
	public NoQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
	
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
	
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	}
	
	public void dispense() {
		System.out.println("You need to pay first");
	}
	
	public String toString() {
		return "waiting for quarter";
	}
 
}

Creating a Real Subject:

import java.rmi.*;
import java.rmi.server.*;
 
/* UnicastRemoteObject
	This gives the ability to act as a remote service. */
 
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {
 
	private static final long serialVersionUID = 2L;
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;
	State state = soldOutState;
	int count = 0;
	String location;
 
	/* As superclass throws RemoteException, this constructor also needs */
	public GumballMachine(String location, int numberGumballs) throws RemoteException {	
		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);
		this.count = numberGumballs;	
		if (numberGumballs > 0) {
			state = noQuarterState;		
		}
		this.location = location;
	}
 
	public void insertQuarter() {
		state.insertQuarter();
	}
	
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}
	
	void setState(State state) {
		this.state = state;
	}
 
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;		
		}
	}
 
	public void refill(int count) {
		this.count = count;
		state = noQuarterState;
	}
 
	public int getCount() {
		return count;
	}
	
	public State getState() {
		return state;
	}
	
	public String getLocation() {
		return location;
	}
	
	public State getSoldOutState() {
		return soldOutState;
	}
 
	public State getNoQuarterState() {
		return noQuarterState;
	}
	
	public State getHasQuarterState() {
		return hasQuarterState;
	}
 
	public State getSoldState() {
		return soldState;
	}
 
	public State getWinnerState() {
		return winnerState;	
	}
	
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2014");
		result.append("\nInventory: " + count + " gumball");
		
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

Setting up RMI registry and running Real Subject:

import java.rmi.*;
 
public class GumballMachineTestDrive {
	public static void main(String[] args) {
		GumballMachineRemote gumballMachine = null;
		int count;
		
		if (args.length < 2) {	
			System.out.println("GumballMachine <name> <inventory>");
			System.exit(1);		
		}
 
		try {
			count = Integer.parseInt(args[1]);	
			gumballMachine = new GumballMachine(args[0], count);
			/* Publishing Stub */
			Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

Run with

  1. rmiregistry
  2. java GumballMachineTestDrive austin.mightygumball.com 100

Creating a Proxy Client:

/* Import RMI package because we are using the RemoteException class below */
import java.rmi.*;
 
public class GumballMonitor {
	GumballMachineRemote machine;
	
	public GumballMonitor(GumballMachineRemote machine) {
		this.machine = machine;
	}
	
	public void report() {
		try {
			System.out.println("Gumball Machine: " + machine.getLocation());
			System.out.println("Current inventory: " + machine.getCount() + " gumballs");
			System.out.println("Current state: " + machine.getState());
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
}

Connecting to Proxies:

import java.rmi.*;
 
public class GumballMonitorTestDrive {
	public static void main(String[] args) {
		
		/* We create an array of locations, one for each machine */
		String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
		"rmi://boulder.mightygumball.com/gumballmachine",
		"rmi://austin.mightygumball.com/gumballmachine"};
		
		if (args.length >= 0) {
			location = new String[1];
			location[0] = "rmi://" + args[0] + "/gumballmachine";
		}
	
		/* We also create an array of monitors */
		GumballMonitor[] monitor = new GumballMonitor[location.length];
	
		for (int i=0;i < location.length; i++) {
			try {	
				/* This returns a proxy to the remote machine.
				Naming.lookup() is a static method in the RMI package that takes a location and
				service name and looks it up in the rmiregistry at that location. */
				GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup(location[i]);
				monitor[i] = new GumballMonitor(machine);
				System.out.println(monitor[i]);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/* Then we iterate through each machine and print out its report */
		for (int i=0; i < monitor.length; i++) {
			monitor[i].report();
		}
	}
}