The serverside architecture can be taught of as two use cases. The first is to find the factor of a mersenne number to check if it is a mersenne prime or not. The second it to take a mersenne prime number and calculated the corresponding perfect number.
The first of these two use cases, find the factors of a mersenne number is by far the more complicated of the two and can be taught of in terms of a number on sub use cases. It involves taking in a mersenne number and finding it factors, since this is computationally a very intensive process, the client is informed of the result asynchronously by email when the calculation is finished.
Furthermore this now involves the notion of state. Since with multiple client invocations, each mersenne number, and it corresponding email address and factors (once it has finished factorising) , must be remembered. Since Web Service are largely stateless services (a consequence of using http as the stateless protocol) then this stateless service must facade some type of stateful service. The client must also be informed of the results, so there must be the ability to email the results to the calling client. Finally some sort of state lifecycle management is also desirable. On a server restart the mersenne number not finished factorising should at a minimum be restarted. The sub use cases can therefore me details at follows:
These uses case are discussed in more detail below. However before discussion them individually it is worth looking at an over view of the sequence diagrams and class diagrams for this use case.
The basic service is the PerfectCalc
interface (here is the WSDL). It defines two method:
public boolean checkMersenneNumber(MersenneNumber k, String email)
takes a mersenne number (a number of the form (2^n -1) not necessarily a prime number) and an email address or the invoker, and factors the mersenne number and emails the result to the invokers. If the mersenne number has no other factor other than itself and one, then it is a mersenne prime
public PerfectNumber calcPerfectNumber(MersennePrime mersennePrime)
takes a mersenne prime and calculates the corrosponding perfect numberHowever, the second method belongs to the second use case and will be discussed below.
The FactorFactory
object is factory pattern responsible for making the stateless PerfectCalc
service stateful. The FactorFactory
object is also a singleton with a protected constructor and a public static initialiser. Here is the code listing for the FactorFactory
object.
/** * A Factory class that is also a singleton object for creating multiple instances of the stateful Factor class * @testcase test.TestFactorFactory * @version 1.0 * @author Eoin Lane */ public class FactorFactory implements IFactorFactory, Observer { /** This contructor is only called once and when it is it the Caretaker is created and initalised */ protected FactorFactory() { // Initalise the Map this.map = new HashMap(); //factors = new Vector(); caretaker = new SimpleCaretaker(); System.out.println("Caretaker created and initialised"); } /** * Factory method to create instances of IFactor object * @param mersenneNumber number of the form (2^k-1) * @param email of the invoking client */ public IFactor getFactor(MersenneNumber mersenneNumber, String email) { // Store the Mersenne number k value into the Map Double d = new Double(mersenneNumber.getK()); this.map.put(d, email); Factor factor = new Factor(mersenneNumber); factor.attach(this); factor.factorize(); // Create a Memento of the newly created factor object IMemento memento = new SimpleMemento(mersenneNumber.getK(), email); //Tell the caretaker about it this.caretaker.addElement(memento); return (IFactor)factor; } /** Returns a one and only one instance of this class */ public static FactorFactory getInstance() { if (instance == null) { synchronized(FactorFactory.class) { if (instance == null) { instance = new FactorFactory(); } } } return instance; } /** * The callback method that the subject will call on this listener * @param factor the finished Factor class */ public void update(Factor factor) { // Get the k value from the mersenne number in the factor class double k_value = factor.getMersenneNumber().getK(); System.out.println("Observer informed of finished calculation of mersenne number: " + k_value); // Get the email corrosponding th the Factor's Mersenne k value String email = (String)(this.map.get(new Double(k_value))); Collection bag = factor.getFactors(); mail(bag, email, k_value); // since this Factor class has now finished process remove it it's corrosponding memento object from the caretaker // Create a Memento of the newly created factor object IMemento memento = new SimpleMemento(k_value, email); this.caretaker.removeElement(factor); } private static FactorFactory instance = null; // Looks after and tracks all the created Factor objects private ICaretaker caretaker; // A map containing the Mersenne number, email value pair Map map; }
The important things to note is the protected constructor where a Vector
of Factor
class is created and initialised. This Vector
keeps a record of all the created Factor
classes. The Factor
classes are then created with the getFactor(MersenneNumber mersenneNumber)
method. This pattern of using a singleton factory object to create and keep track of the generated Factor
object, allows a stateless facade to be exposed to the outside while internally stateful classes can be created and their life cycle managed.
The observer pattern is used here as a callback to the the FactorFactory
object to tell it that the Factor
obejct created to factorise a particular mersenne number is now finished. To achieve this FactorFactory implements the observer interface while tthe Factor object implements the
/** * This object is used to factorise a mersenne number. There are potentially many of these created in memory at run time. * @testcase test.TestFactor * @author Eoin Lane * @version 1.0 */ public class Factor implements IFactor, Subject { /** * Constructor * @param mersenneNumber a number of the form (2^k-1) */ public Factor(MersenneNumber mersenneNumber) { strategy = new LLFactorer(); this.mersenneNumber = mersenneNumber; } /** Factorise this object */ public void factorize() { factors = this.strategy.factorize(this.mersenneNumber); this.inform(); } public MersenneNumber getMersenneNumber() { return this.mersenneNumber; } /** * Register an observer with this object. * @param observer an observer */ public void attach(Observer observer) { observersVector.addElement(observer); } /** * detach an observer from this object. * @param observer an observer */ public void detach(Observer observer) { observersVector.removeElement(observer); } /** Inform all the observers of an event */ public void inform() { java.util.Enumeration enumeration = observers(); Collection bag = this.getFactors(); while (enumeration.hasMoreElements()) { ((Observer)enumeration.nextElement()).update(this); } } public Enumeration observers() { return ((java.util.Vector) observersVector.clone()).elements(); } public Collection getFactors() { return factors; } /*# private FactorFactory _factorFactory; */ // The (2^k-1) object private MersenneNumber mersenneNumber; // The factors returned by the factorisation method private Collection factors; // The strategy to be used to factorise this object private IFactorer strategy = null; /** @associates <{Observer}> */ private Vector observersVector = new java.util.Vector(); }
A strategy pattern is used to implement the factorisation. The strategy pattern allows an algorithmic change of the implemention at run time, depending on the best choice of strageties (i.e.a different algorithm maybe used depending on the size of the number to be factored). Here is the code for LLFactoer
a simple algormetic implementation of a the IFactor
inferface.
import java.util.Collection; import primes.PrimeNumber; import java.lang.Math; import perfectNumbers.MersenneNumber; /** * A simple concrete implementation of the strategy IFactorer interface * @testcase test.TestLLFactorer * @author Eoin Lane * @version 1.0 */ public class LLFactorer implements IFactorer { public Collection factorize(Number number) { return new java.util.ArrayList(null); } // We can check a potential factor n of Mp by seeing if 2^p (mod n) =1 public Collection factorize(MersenneNumber mersenneNumber) { System.out.println("Got Mersenne number for processsing"); // A Collection bag to collect the factors Collection bag = new java.util.ArrayList(); double maxNumberOfIteration = Math.pow((double)2, (double)(mersenneNumber.getK())); double siveNumberOfIteration = sive(maxNumberOfIteration); for (long i = 1; i < siveNumberOfIteration; i++) { if ((factorize(mersenneNumber.getK(), (long)i) == 0)) { bag.add(new Long(i)); System.out.println(i + "\t" + factorize(mersenneNumber.getK(), i)); } } return bag; } //a simple algorithm for checking for find a^b (mod c) public static double factorize(double b, double c) { double a = 2; double res = 1; double d; while (b > 0) { d = b % 2; if (d == 1) { res = a * res; if (res > c) res = res % c; } a = a * a; if (a > c) a = a % c; b = (b - d) / 2; } return res; } // A simple sive to cut down the number of potential factors public static double sive(double a) { return (a + 1) / 2; } }
The Factor
object calls the public Collection factorize(MersenneNumber mersenneNumber)
method defined in the Factorer
interface to do the factorisation. The LLFactorer
object (shown above) implements this method by first calling the public static long sive(long a)
method to cut down on the potential factor. This is a very simple method that just cuts the number of factors in half (i.e. if (2^4-1)=15 is to be checked, then only factors up to half that number need to be checked). These potential factors are then iterated through and individually checked using the factorized algorithm found in the public static double factorize(double b, double c)
method. Most of the material for the above strategy code can be found the following Mersenne FAQ
In case of a server restart the current mersenne numbers being factored need to be persisted. To achieve this a
memento pattern
is used. This is conjenction with a Caretaker
object which manages and persists the Memeto
objects. The Memento
objects represent the state of the Factor
objects currently being computed and the Caretaker
object manages and persists these object. On startup the Caretaker
checks if all the previous Factor
objects had finished. Unfinished Factor
object are reinitialised and their calculations restated. In a later iteration, the stage of factorization will also be persisted.
/** * A simple interface of the Caretaker interface. This Caretaker implementation is used to persist Factor object by keeping a * list of Memento representing the state of the current Factor instance. It writes that list to disk using a a JDO DAO object * @author Eoin Lane * @version 1.0 */ public class SimpleCaretaker implements ICaretaker { /** The current list of Memento objects */ private Vector currentList; /** The initalisign list of factor objects */ private Vector factors; /** * @link dependency * @label mementos */ /*# IMemento lnkIMemento; */ /** * Initialise the caretaker load Memento object using JDO from disk if necessary * @param a vector of factory objects */ public SimpleCaretaker(Vector factors) { this.currentList = new Vector(); this.factors = factors; System.out.println("Loading Memento objects using JDO from if necessary disk"); } /** Clears the current Vector list */ public void clear(Vector factor) { currentList = new Vector(); this.factors = factors; } /** Adds an element */ public void addElement(Object obj) { currentList.addElement(obj); } /** Removes an element */ public void removeElement(Object obj) { currentList.removeElement(obj); } //------------------------------------ private void remove(IMemento obj) { IMemento m = (IMemento)obj; m.restore(); //and restore the old position } }
The persistence mechanism used here is the new java data object mechanism and since this is a relatively new topic, here is a simple resource to get stated http://www.jdocentral.com/index.html. A JDO data access object (DAO) is used to encapsulate the persistence mechanism and the Caretaker
obejct uses the JDODOA
object to persist the object graph of Menento
objects (these Memento
objects contain the state of the Factor
objects currently in memory)
This use is also defined in the IPerfectCalc
interface and implemented in PerfectCalc
. As a reminder, here are the method delcared in the IPerfectCalc
interface again: It defines two method:
public boolean checkMersenneNumber(MersenneNumber k, String email)
takes a mersenne number (a number of the form (2^n -1) not necessarily a prime number) and an email address or the invoker, and factors the mersenne number and emails the result to the invokers. If the mersenee number has no other factor other than itself and one, then it is a mersenne prime
public PerfectNumber calcPerfectNumber(MersennePrime mersennePrime)
takes a mersenne prime and calculates the corresponding perfect numberAs is evident from the service description this, service takes a mersenne prime and returns the corresponding perfect number. Here is the sequence diagrams and class diagram for this use case.
This server side architecture used a number of GOF patterns and is detailed below.
A stateless facde service class expose to the client via a Web Service. This facde calls the FactorFactory
(which is a factory class, a mediator and a singleton) to create a Factor
stateful objects. There will be potentially many of these objects, depending on the number of client requests from factorization. The Factor
stateful object uses a strategy pattern to find the factors of the mersenne number and informs the FactorFactory
of the state change using an observer pattern. The FactorFactory
will then email the result. Finally the FactorFactory
also uses a memento pattern to persist the sate of the mersenne number factorisation calculation, so the it may be reinstated in the case of a server restart.
Here is a list of and references to the GOF patterns used:
FactorFactory
object for creating Factor (or any other type of object implementing the IFactor interface) at runtime.
FactorFactory
object to ensure there is only one object creating and managing Factor
objectsFactorFactory
again to control and manage the life cycle of the Factor
objects that it is responsible for creating.