This article shows how to call Mediator Web Services from Java. The example uses XML-to-Java Object mapping techniques using JAXB 2.0 (available as part of Java SE 6). For an introduction to the Mediator XML structure please read the previous article "Introducing Pharos Mediator Web Services - Accessing with PHP"
Source code for this article is available for download
One item missing from the previous document was a discussion and example of login. It is possible to configure Mediator to allow certain methods to be called without having to login - this was the case with the PHP example, however it is not normally how Mediator is set up. Therefore to access any method you first have to login. Here is a PharosCs document for the login method:
<PharosCs>
<CommandList>
<Command subsystem="login" method="login">
<ParameterList>
<Parameter name="userName" value="bob"/>
<Parameter name="password" value="secret"/>
<Parameter name="clientId" value="Bob's software 1.0"/>
<Parameter name="apiVersion" value="4.1"/>
</ParameterList>
</Command>
</CommandList>
</PharosCs>
Four parameters are used in this example: userName and password are obvious, clientId is used to identify this client for logging and monitoring purposes and apiVersion is used to tell Mediator which version of API the client was developed against. This last parameter can be used by Mediator to ensure backwards compatibility - if a new version of a method is developed which is no longer compatible with a previous version (say a new required parameter is added) then the older version can be used instead.
If your username and password are correct then the response document will be sent containing your session key to use in all subsequent method calls:
<PharosCs>
<CommandList>
<Command subsystem="login" method="login" type="response" success="true">
<Output>f88071e6-a807-4779-8875-28eca0211df6</Output>
</Command>
</CommandList>
</PharosCs>
Part of the example shows how to login and retrieve a session key like this:
PharosCs result = jaxbExample.sendPharosCs(
new PharosCs("No session key yet",
new Command("login","login","ref",
new Parameter("userName", "bob"),
new Parameter("password", "secret"),
new Parameter("clientId", "JAXB Example")
)
)
);
Command c = result.commandList.commands.get(0);
String sessionKey = c.output.get(String.class);
Here you can see that the main elements of the XML API are modelled as Java classes: PharosCs, Command and Parameter. These classes have been given convenient constructors to allow the code to be written as above. In the example they have all been written as static inner classes like so:
@XmlRootElement(name="PharosCs")
static class PharosCs {
PharosCs() {
// No-arg constructor required for JAXB
}
PharosCs(String sessionKey, Command... commands) {
this.commandList = new CommandList(sessionKey, commands);
}
@XmlElement(name="CommandList")
CommandList commandList;
}
static class CommandList {
CommandList() {
// No-arg constructor required for JAXB
}
CommandList(String sessionKey, Command... commands) {
this.sessionKey = sessionKey;
this.commands = Arrays.asList(commands);
}
@XmlAttribute
String sessionKey;
@XmlElement(name="Command")
List commands;
}
static class Command {
Command() {
// No-arg constructor required for JAXB
}
Command(String subsystem, String method, String reference) {
this.subsystem = subsystem;
this.method = method;
this.reference = reference;
}
Command(String subsystem, String method, String reference,
Parameter... parameters) {
this(subsystem, method, reference);
this.parameters = Arrays.asList(parameters);
}
@XmlAttribute
String method;
@XmlAttribute
String reference;
@XmlAttribute
String subsystem;
@XmlAttribute
Boolean success;
@XmlAttribute
String type;
@XmlElementWrapper(name="ParameterList")
@XmlElement(name="Parameter")
List parameters;
@XmlElement(name="Output")
Output output;
}
static class Parameter {
Parameter() {
// No-arg constructor required for JAXB
}
Parameter(String name, Object value) {
this.name = name;
this.value = new ParameterValue(value);
}
@XmlAttribute
String name;
@XmlElement(name="Value")
ParameterValue value;
}
static class ParameterValue {
ParameterValue() {
// No-arg constructor required for JAXB
}
ParameterValue(Object value) {
this.objects = new ArrayList<Object>();
this.objects.add(value);
}
@XmlAnyElement(lax=true) @XmlMixed
List<Object> objects;
}
You can see that the constructors are all just standard Java 5+ code (varargs are used for the CommandList and Command constructors to allow you to pass in a number of Commands or Parameters).
JAXB 2.0 annotations have been applied to the classes above to provide the mapping between Java and XML. The documentation here [jaxb.dev.java.net] and the Java SE 6 JavaDoc here [java.sun.com] explains these annotations.
All the annotations are straight-forward but it is important to note that in the ParameterValue class the @XmlAnyElement(lax=true) @XmlMixed annotations have been used. This allows any elements, including text elements, to appear as the content of the Value element for Parameters.
In order to call a web service the PharosCs objects have to be marshalled to XML and then sent to Mediator over HTTP. The result XML returned from Mediator can then be unmarshalled to PharosCs objects. In order to achieve this we have to set up some JAXB objects which in the example is done in the constructor of the main class:
private final JAXBContext jaxbContext;
private final Unmarshaller unmarshaller;
private final Marshaller marshaller;
JaxbExample() {
try {
jaxbContext = JAXBContext.newInstance(PharosCs.class,
CommandException.class, Material.class);
unmarshaller = jaxbContext.createUnmarshaller();
marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
Boolean.TRUE);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
Three objects are required: the JAXBContext with which we register all the "root element" classes, an Unmarshaller and a Marshaller. (Note: for this example I have set the JAXB_FORMATTED_OUPUT to true which adds indents and line breaks to the XML output. This is nice while debugging but you would not typically enable this in a live system.)
So we can now put this altogether and create a method to send the PharosCs object to Mediator over HTTP and return the response PharosCs object:
private PharosCs sendPharosCs(PharosCs inPharosCs) {
try {
URL u = new URL("http://localhost/mediator/main/ws");
URLConnection uc = u.openConnection();
HttpURLConnection connection = (HttpURLConnection) uc;
connection.setRequestProperty("Content-Type",
"text/xml; charset=\"utf-8\"");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setRequestMethod("POST");
OutputStream out = connection.getOutputStream();
marshaller.marshal(inPharosCs, out);
out.close();
InputStream in = connection.getInputStream();
PharosCs pharosCs = (PharosCs) unmarshaller.unmarshal(in);
connection.disconnect();
return pharosCs;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Here you can see that we make an HTTP connection to http://localhost/mediator/main/ws, your server will obviously not be localhost but the rest of the URL will be the same. We also set the Content-Type property to text/xml; charset="utf-8" and the request method to POST. The Marshaller object is used to pass the XML directly to the OutputStream. The InputStream containing the response XML is then passed directly to the Unmarshaller object to convert it to a PharosCs object which is then returned.
Now we need to introduce the Output object and the first of the two root element objects used in this example: CommandException.
static class Output {
@XmlAnyElement(lax=true) @XmlMixed
List<Object> objects;
@SuppressWarnings("unchecked")
<T> get(Class<T> clazz) throws Exception {
for (Object o : objects) {
if (o.getClass() == clazz) {
return (T)o;
} else if (o.getClass() == CommandException.class) {
throw new Exception("Command Exception found: "+
(CommandException)o);
}
}
throw new Exception("Class not found");
}
}
@XmlRootElement(name="CommandException")
static class CommandException {
private static final long serialVersionUID = 1L;
@XmlElement(name="Message")
String message;
@XmlElement(name="Code")
String code;
@Override
public String toString() {
return "Code=["+code+"] Message=["+message+"]";
}
}
The Output object is another container for any elements like ParameterValue discussed earlier. The get method has been written to allow you to extract an object of the expected type from this container. For example, the expected result from the call to login is a string containing the session-key so you can call get like this:
String sessionKey = output.get(String.class);
If a CommandException is found in the container instead of the expected type then an Exception is thrown. If the expected type is not found then an Exception is also thrown.
Note that the CommandException class has been annotated with @XmlRootElement and was included in the JAXBContext creation earlier. This makes it available as one of the possible elements that could appear in the Output.
Finally we can make the program do something useful. It will login, retrieve a Material object and then logout. To do this we first have to define another root element object, Material:
package tv.pharos.ws;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Material")
public class Material {
private String matId;
private String title;
private String duration;
private String aspectRatio;
private String materialType;
@XmlElement(name="MatId")
public String getMatId() {
return matId;
}
public void setMatId(String matId) {
this.matId = matId;
}
@XmlElement(name="Title")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@XmlElement(name="Duration")
public String getDuration() {
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
@XmlElement(name="AspectRatio")
public String getAspectRatio() {
return aspectRatio;
}
public void setAspectRatio(String aspectRatio) {
this.aspectRatio = aspectRatio;
}
@XmlElement(name="MaterialType")
public String getMaterialType() {
return materialType;
}
public void setMaterialType(String materialType) {
this.materialType = materialType;
}
@Override
public String toString() {
return "MatId=["+matId+"] Title=["+title+"] Duration=["+duration+
"] AspectRatio=["+aspectRatio+"] MaterialType=["+
materialType+"]";
}
}
This class is a simple JavaBean with JAXB annotations on the getter methods rather than the fields.
Now we can use the Material class in the JAXBContext and retrieve it from Mediator by calling the material.get method. Here is the main method which does just that:
public static void main(String[] args) {
JaxbExample jaxbExample = new JaxbExample();
PharosCs result;
Command c;
try {
// Login to get a session key
result = jaxbExample.sendPharosCs(
new PharosCs("No session key yet",
new Command("login","login","ref",
new Parameter("userName", "bob"),
new Parameter("password", "secret"),
new Parameter("clientId", "JAXB Example")
)
)
);
c = result.commandList.commands.get(0);
String sessionKey = c.output.get(String.class);
System.out.println("Got session key: "+sessionKey);
// Get a material
result = jaxbExample.sendPharosCs(
new PharosCs(sessionKey,
new Command("material","get","getPHDM60032",
new Parameter("matId", "PHDM60032")
)
)
);
c = result.commandList.commands.get(0);
System.out.println("Got Material: "+c.output.get(Material.class));
// Logout
result = jaxbExample.sendPharosCs(
new PharosCs(sessionKey, new Command("login","logout","ref")));
c = result.commandList.commands.get(0);
System.out.println("Log out message: "+c.output.get(String.class));
} catch (Exception e) {
e.printStackTrace();
}
}
And here is the output:
Got session key: 2eb9f3c3-4ff9-47a7-be70-a6f213805eba
Got Material: MatId=[PHDM60032] Title=[Cyclists Special] Duration=[00:15:27:05]
AspectRatio=[16:9] MaterialType=[Programme]
Log out message: Logged out
Jeremy Blythe
Chief Software Architect
Pharos Communications