Prepare an suitable version of Eclipse for the forthcoming labs. Download and become familiar with the pacemaker-console project. Explore the Strategy pattern in this context.
Download and install the following:
Download the latest Eclipse for DSL Developers
From this site:
in particular this download page:
... and Download and install the latest non-activator version of the Play framework (currently 2.2.6)
From this site:
Download an install the latest Android Studio 2 build.
Download and import this project into your eclipse workspace:
This is a java version of the pacemaker project you may be familiar with.
The valid commands are documented here:
In addition the above commands, entering '!la' will list all of the commands:
!la
abbrev name params
!el !enable-logging (fileName)
!dl !disable-logging ()
!rs !run-script (filename)
!gle !get-last-exception ()
!sdt !set-display-time (do-display-time)
?l ?list (startsWith)
?l ?list ()
?h ?help (command-name)
?h ?help ()
?la ?list-all ()
?ghh ?generate-HTML-help (file-name, include-prefixed)
l load ()
s store ()
lu list-users ()
cu create-user (first name, last name, email, password)
lu list-user (email)
lius list-user (id)
la list-activities (user id)
du delete-user (id)
aa add-activity (user-id, type, location, distance, datetime, duration)
al add-location (activity-id, latitude, longitude)
pm>
and the !rs
command will run a specific script. So, if we have a script like this is test.script
:
cu homer simpsom homer@simpson.com secret
cu marge simpson marge@simpson.com secret
aa 1 walk fridge .001 "11:12:2013 11:20:00" 01:20:12
aa 1 walk bar 1.0 "13:12:2013 02:20:00" 00:00:00
aa 1 run work 2.2 "14:12:2013 03:20:00" 01:10:00
aa 2 walk shop 2.5 "15:12:2013 10:20:00" 02:03:00
aa 2 cycle shop 4.5 "16:12:2013 08:20:00" 03:03:00
al 3 23.3 32.3
al 3 23.3 32.5
al 3 23.3 32.6
and we execute:
!rs test.script
then the above users/activities/locations will be added.
If you then enter:
save
.. and refresh the eclipse workspace, it should reveal 'datastore.xml' containing the above entries.
If reatart the program and enter
load
it should restore the entries. Experiment with this now until you are happy it functions as expected.
The serialization has been centralised into this class:
public class XMLSerializer
{
private Deque<Object> stack = new ArrayDeque<>();
private File file;
public XMLSerializer(String filename)
{
this.file = new File(filename + ".xml");
}
public void push(Object o)
{
stack.push(o);
}
public Object pop()
{
return stack.pop();
}
@SuppressWarnings("unchecked")
public void read() throws Exception
{
ObjectInputStream is = null;
try
{
XStream xstream = new XStream(new DomDriver());
is = xstream.createObjectInputStream(new FileReader(file));
stack = ( Deque<Object>) is.readObject();
}
finally
{
if (is != null)
{
is.close();
}
}
}
public void write() throws Exception
{
ObjectOutputStream os = null;
try
{
XStream xstream = new XStream(new DomDriver());
os = xstream.createObjectOutputStream(new FileWriter(file));
os.writeObject(stack);
}
finally
{
if (os != null)
{
os.close();
}
}
}
}
This is engaged by the PacemakerAPI load/store methods:
@SuppressWarnings("unchecked")
public void load() throws Exception
{
serializer.read();
activityIndex = (Long) serializer.pop();
userIndex = (Long) serializer.pop();
activityMap = (Map<Long, Activity>) serializer.pop();
userEmailMap = (Map<String, User>) serializer.pop();
userMap = (Map<Long, User>) serializer.pop();
users = userMap.values();
}
public void store() throws Exception
{
serializer.push(userMap);
serializer.push(userEmailMap);
serializer.push(activityMap);
serializer.push(userIndex);
serializer.push(activityIndex);
serializer.write();
}
Which are in turn invoked from PacemakerShell:
@Command(description="Load activities persistent store")
public void load () throws Exception
{
paceApi.load();
}
@Command(description="Store activities persistent store")
public void store () throws Exception
{
paceApi.store();
}
Make sure you understand the above mechanisms. Perhaps debug into the running program and inspect the program state as you go.
We would like to support a new command - cff
- short for change file format. This command is to permit xml or json serialisation format.
To save in xml format:
cff xml
save
to save in json:
cff json
save
to load from either:
cff json
load
cff xml
load
This is the implementation of the command in the shell class:
@Command(description="Set file format")
public void changeFileFormat (@Param(name="file format: xml, json") String fileFormat)
{
if (fileFormat.equals("xml"))
paceApi.serializer = xmlSerializer;
else if (fileFormat.equals("json"))
paceApi.serializer = jsonSerializer;
}
Notice that we now have 2 seralizers - one for XML and one for JSON.
Here is an implementation of a JSON serializer :
package utils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Deque;
import java.util.ArrayDeque;
public class JSONSerializer
{
private Deque<Object> stack = new ArrayDeque<>();
private File file;
public JSONSerializer (String filename)
{
this.file = new File(filename + ".json");
}
@Override
public void push(Object o)
{
stack.push(o);
}
@Override
public Object pop()
{
return stack.pop();
}
@SuppressWarnings("unchecked")
@Override
public void read() throws Exception
{
ObjectInputStream is = null;
try
{
XStream xstream = new XStream(new JettisonMappedXmlDriver());
is = xstream.createObjectInputStream(new FileReader(file));
stack = ( Deque<Object>) is.readObject();
}
finally
{
if (is != null)
{
is.close();
}
}
}
public void write() throws Exception
{
ObjectOutputStream os = null;
try
{
XStream xstream = new XStream(new JettisonMappedXmlDriver());
os = xstream.createObjectOutputStream(new FileWriter(file));
os.writeObject(stack);
}
finally
{
if (os != null)
{
os.close();
}
}
}
}
Incorporate this class + the above command into your project.
It will have errors (see next step)
See if you can complete the implementation such that the cff
command works as expected.
This will require:
The overall effect should be to keep knowledge of the specific serializer away from the PacemanerAPI class.
The solution is presented in the next step.
This is a solution:
These three commits encapsulate the application of the pattern:
Verify that this is equivalent to your solution.
Consider introducing a new serializer to support the YAML file format:
This is a prominent java YAML library:
Here is a version of a write
method that will serialize our stack data structure:
public void write() throws Exception
{
Yaml yaml = new Yaml();
FileWriter writer = new FileWriter(file);
for (Object o : stack)
{
yaml.dump (stack.pop(), writer);
}
writer.close();
}
Try this out and explore the generated output.
Is it possible to seamlessly integrate this into pacemaker as another serializer?