Commit 67620baf authored by R.W.Majeed's avatar R.W.Majeed

client prototype working

parent ed78d8ae
package de.sekmi.histream.li2b2.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Objects;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class CellClient {
private static final Logger log = Logger.getLogger(CellClient.class.getName());
protected Client client;
protected URL serviceUrl;
public CellClient(Client client, URL serviceUrl){
this.client = client;
this.serviceUrl = serviceUrl;
}
protected String getOutputCharset(){
return client.getOutputEncoding();
}
protected URL createRequest(String path) throws MalformedURLException{
return new URL(serviceUrl, path);
}
protected DocumentBuilder newBuilder(){
return client.newBuilder();
}
/**
* Create a new request message. Will also set the security
* credentials.
*
* @param builder document builder
* @return request message
*/
protected Request createRequestMessage(DocumentBuilder builder){
Request req = client.createRequest(builder);
req.setSecurity(client.credentials);
req.setProjectId(client.getProjectId());
// TODO set message id
return req;
}
protected Request createRequestMessage(){
return createRequestMessage(newBuilder());
}
protected Request createRequestMessage(String bodyXML) throws SAXException, IOException{
DocumentBuilder b = newBuilder();
Request req = createRequestMessage(b);
// parse body XML
Document dom = b.parse(new InputSource(new StringReader(bodyXML)));
Element body = req.getMessageBody();
Objects.requireNonNull(body, "no message_body");
// move parsed DOM into message_body
body.appendChild(body.getOwnerDocument().adoptNode(dom.getDocumentElement()));
return req;
}
/**
* Create the HTTP connection. Will use the proxy specified by
* the client and fill the redirect URL in the message header.
* <p>
* This method will also prepare the returned connection for
* the {@link URLConnection#connect()} call. Specifically,
* the request method will be set to {@code POST}, and the
* {@code Content-Type} header will be set to {@code application/xml}
* with output charset.
* </p>
* @param request request
* @param requestUrl URL
* @throws IOException io error
*/
protected HttpURLConnection createConnection(Request request, URL requestUrl) throws IOException{
HttpURLConnection c;
if( client.getProxy() != null ){
request.setRedirectUrl(requestUrl);
c = (HttpURLConnection)client.getProxy().openConnection();
}else{
// clear redirect URL
request.setRedirectUrl(null);
c = (HttpURLConnection)requestUrl.openConnection();
}
c.setRequestMethod("POST");
c.setDoOutput(true);
c.setRequestProperty("Content-Type", "application/xml; charset="+getOutputCharset());
return c;
}
protected Response submitRequest(Request request, String method) throws MalformedURLException, IOException{
return submitRequest(newBuilder(), request, createRequest(method));
}
protected Response submitRequest(DocumentBuilder b, Request request, URL requestUrl) throws IOException{
HttpURLConnection c = createConnection(request, requestUrl);
c.connect();
OutputStream out = c.getOutputStream();
try {
log.info("Submitting to "+requestUrl);
DOMUtils.printDOM(request.dom, System.out);
DOMUtils.printDOM(request.dom, out, getOutputCharset());
} catch (TransformerException e) {
throw new IOException(e);
}
out.close();
int status = c.getResponseCode();
// check status code for failure
if( status != 200 ){
throw new IOException("Unexpected HTTP response code "+status);
}
// System.out.println("Response:"+status);
InputStream in = c.getInputStream();
Document resp;
try {
resp = b.parse(in);
DOMUtils.stripWhitespace(resp.getDocumentElement());
} catch (SAXException | XPathExpressionException e) {
throw new IOException("Unable to parse response XML",e);
}
log.info("Received response:");
DOMUtils.printDOM(resp, System.out);
return new Response(resp);
}
}
package de.sekmi.histream.li2b2.client;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import de.sekmi.histream.li2b2.client.pm.UserConfiguration;
import de.sekmi.histream.li2b2.client.ont.OntologyClient;
import de.sekmi.histream.li2b2.client.pm.Cell;
import de.sekmi.histream.li2b2.client.pm.PMClient;
public class Client {
private static final Logger log = Logger.getLogger(Client.class.getName());
// configuration for connection
private URL proxy;
// information from server
UserConfiguration info;
Credentials credentials;
String projectId;
private PMClient pm;
private OntologyClient ont;
private Document requestTemplate;
private DocumentBuilderFactory factory;
private String outputEncoding;
public Client() throws IOException{
factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
this.outputEncoding = "UTF-8";
DocumentBuilder b;
try {
b = factory.newDocumentBuilder();
requestTemplate = b.parse(getClass().getResourceAsStream("/request_template.xml"));
DOMUtils.stripWhitespace(requestTemplate.getDocumentElement());
} catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
throw new IOException(e);
}
}
public void setProjectId(String projectId){
this.projectId = projectId;
}
public String getProjectId(){
return this.projectId;
}
public void setProxy(URL proxy){
this.proxy = proxy;
}
public URL getProxy(){
return this.proxy;
}
public String getOutputEncoding(){
return outputEncoding;
}
public void setAuthorisation(String user, String password, String domain, boolean isToken){
setAuthorisation(new Credentials(domain, user, password, isToken));
}
public void setAuthorisation(Credentials credentials){
this.credentials = credentials;
}
DocumentBuilder newBuilder(){
try {
return factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
public void setPM(URL pmService){
this.pm = new PMClient(this, pmService);
}
public void setONT(URL url){
this.ont = new OntologyClient(this, url);
}
public PMClient PM(){
return this.pm;
}
public OntologyClient ONT(){
return this.ont;
}
public Request createRequest(DocumentBuilder builder){
Document req = builder.newDocument();
req.appendChild(req.importNode(requestTemplate.getDocumentElement(), true));
Request r = new Request(req);
// TODO random message id
r.setMessageId("asdf", "0");
return r;
}
public void setServices(Cell[] cells){
for( int i=0; i<cells.length; i++ ){
try {
switch( cells[i].id ){
case "ONT":
setONT(new URL(cells[i].url));
break;
default:
log.info("Ignoring unsupported cell "+cells[i].id+": "+cells[i].name);
}
} catch (MalformedURLException e) {
log.log(Level.WARNING,"illegal URL for cell "+cells[i].id+":"+cells[i].url, e);
}
}
}
}
package de.sekmi.histream.li2b2.client;
public class Credentials {
private String user;
private String domain;
private String password;
private boolean isToken;
public Credentials(String domain, String user, String password, boolean isToken){
this.user = user;
this.domain = domain;
this.password = password;
this.isToken = isToken;
}
public String getUser(){
return user;
}
public String getDomain(){
return domain;
}
public boolean isToken(){
return isToken;
}
public String getPassword(){
return password;
}
}
package de.sekmi.histream.li2b2.client;
import java.io.OutputStream;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DOMUtils {
public static void stripWhitespace(Element node) throws XPathExpressionException{
XPathFactory xf = XPathFactory.newInstance();
// XPath to find empty text nodes.
XPathExpression xe = xf.newXPath().compile("//text()[normalize-space(.) = '']");
NodeList nl = (NodeList)xe.evaluate(node, XPathConstants.NODESET);
// Remove each empty text node from document.
for (int i = 0; i < nl.getLength(); i++) {
Node empty = nl.item(i);
empty.getParentNode().removeChild(empty);
}
}
public static void printDOM(Node node, OutputStream out){
try {
printDOM(node, out, "UTF-8");
} catch (TransformerException e) {
throw new RuntimeException(e);
}
}
public static void printDOM(Node node, OutputStream out, String encoding) throws TransformerException{
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer;
transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(new DOMSource(node),
new StreamResult(out));
}
}
package de.sekmi.histream.li2b2.client;
public class HiveException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
private String statusType;
private String statusMessage;
public HiveException(String statusType, String statusMessage){
super(statusType+":"+statusMessage);
this.statusType = statusType;
this.statusMessage = statusMessage;
}
public HiveException(Response.ResultStatus status){
this(status.getCode(), status.getMessage());
}
public String getStatusType(){
return statusType;
}
public String getHiveMessage(){
return statusMessage;
}
}
package de.sekmi.histream.li2b2.client;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public abstract class HiveMessage {
Document dom;
public HiveMessage(Document dom){
this.dom = dom;
}
public Element getMessageHeader(){
Node n = dom.getDocumentElement().getFirstChild();
if( n.getNodeType() != Node.ELEMENT_NODE || !n.getNodeName().equals("message_header") ){
throw new RuntimeException("message_header not found in request template: "+n);
}
return (Element)n;
}
public Element getMessageBody(){
Node n = dom.getDocumentElement().getLastChild();
if( n != null && n.getNodeType() == Node.ELEMENT_NODE && n.getNodeName().equals("message_body") ){
return (Element)n;
}else{
return null;
}
}
}
package de.sekmi.histream.li2b2.client;
public class I2b2Constants {
public static final String PM_NS = "http://www.i2b2.org/xsd/cell/pm/1.1/";
}
package de.sekmi.histream.li2b2.client;
import java.net.URL;
import java.time.Instant;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class Request extends HiveMessage{
public Request(Document dom) {
super(dom);
// set timestamp
getMessageHeader().getElementsByTagName("datetime_of_message").item(0).setTextContent(Instant.now().toString());
}
public Request setSecurity(Credentials credentials){
Element mh = getMessageHeader();
NodeList nl = mh.getElementsByTagName("security").item(0).getChildNodes();
nl.item(0).setTextContent(credentials.getDomain());
nl.item(1).setTextContent(credentials.getUser());
Element e = (Element)nl.item(2);
if( credentials.isToken() ){
e.setAttribute("is_token", "true");
}else{
e.removeAttribute("is_token");
}
e.setTextContent(credentials.getPassword());
return this;
}
public Request setProjectId(String projectId){
Element mh = getMessageHeader();
mh.getElementsByTagName("project_id").item(0).setTextContent(projectId);
return this;
}
public Request setMessageId(String id, String inst){
Element mh = getMessageHeader();
NodeList nl = mh.getElementsByTagName("message_control_id").item(0).getChildNodes();
nl.item(0).setTextContent(id);
nl.item(1).setTextContent(inst);
return this;
}
public Request setRedirectUrl(URL url){
Element mh = getMessageHeader();
if( url != null ){
mh.getFirstChild().getFirstChild().setTextContent(url.toExternalForm());
}else{
// no proxy
// remove proxy element
mh.removeChild(mh.getFirstChild());
}
return this;
}
public Element addBodyElement(String namespaceURI, String qualifiedName){
Element bod = getMessageBody();
return (Element)bod.appendChild(bod.getOwnerDocument().createElementNS(namespaceURI, qualifiedName));
}
}
package de.sekmi.histream.li2b2.client;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class Response extends HiveMessage{
public Response(Document dom) {
super(dom);
}
public Element getResponseHeader(){
return (Element)getMessageHeader().getNextSibling();
}
public ResultStatus getResultStatus(){
Element rh = getResponseHeader();
NodeList nl = rh.getElementsByTagName("result_status");
if( nl.getLength() == 0 ){
return null;
}else{
return new ResultStatus((Element)nl.item(0).getFirstChild());
}
}
public static class ResultStatus{
private Element status;
public ResultStatus(Element status){
this.status = status;
}
public String getCode(){
return status.getAttribute("type");
}
public String getMessage(){
return status.getTextContent();
}
}
@Override
public Element getMessageBody(){
Node n = getResponseHeader().getNextSibling();
if( n != null && n.getNodeType() == Node.ELEMENT_NODE && n.getNodeName().equals("message_body") ){
return (Element)n;
}else{
throw new RuntimeException("No message body in response:"+n);
}
}
}
package de.sekmi.histream.li2b2.client.ont;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="concept")
@XmlAccessorType(XmlAccessType.FIELD)
public class Concept {
public Integer level;
public String key;
public String name;
}
package de.sekmi.histream.li2b2.client.ont;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import de.sekmi.histream.li2b2.client.CellClient;
import de.sekmi.histream.li2b2.client.Client;
import de.sekmi.histream.li2b2.client.HiveException;
import de.sekmi.histream.li2b2.client.Request;
import de.sekmi.histream.li2b2.client.Response;
import de.sekmi.histream.li2b2.client.Response.ResultStatus;
public class OntologyClient extends CellClient {
public static final String XMLNS = "http://www.i2b2.org/xsd/cell/ont/1.1/";
public OntologyClient(Client client, URL serviceUrl) {
super(client, serviceUrl);
// TODO Auto-generated constructor stub
}
public Concept[] getCategories() throws IOException, HiveException{
Request req = createRequestMessage();
// set body
// <ont:get_categories synonyms="true" hiddens="false" type="core"/>
Element el = req.addBodyElement(XMLNS, "get_categories");
el.setAttribute("synonyms", "true");
el.setAttribute("hiddens", "false");
el.setAttribute("type", "core");
// submit
Response resp = submitRequest(req, "getCategories");
ResultStatus rs = resp.getResultStatus();
if( !rs.getCode().equals("DONE") ){
throw new HiveException(rs);
}
// parse concepts
// TODO move to private method since we will need this more often
NodeList nl = resp.getMessageBody().getElementsByTagName("concept");
Concept[] concepts = new Concept[nl.getLength()];
try {
Unmarshaller um = JAXBContext.newInstance(Concept.class).createUnmarshaller();
for( int i=0; i<concepts.length; i++ ){
concepts[i] = (Concept)um.unmarshal(new DOMSource(nl.item(i)));
}
} catch (JAXBException e) {
throw new IOException("error parsing concepts", e);
}
return new Concept[]{};
}
}
package de.sekmi.histream.li2b2.client.pm;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="cell_data")
@XmlAccessorType(XmlAccessType.FIELD)
public class Cell {
@XmlAttribute
public String id;
public String name;
public String url;
public String project_path;
public String method;
public Boolean can_override;
}
package de.sekmi.histream.li2b2.client.pm;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import de.sekmi.histream.li2b2.client.CellClient;
import de.sekmi.histream.li2b2.client.Client;
import de.sekmi.histream.li2b2.client.Credentials;
import de.sekmi.histream.li2b2.client.HiveException;
import de.sekmi.histream.li2b2.client.I2b2Constants;
import de.sekmi.histream.li2b2.client.Request;
import de.sekmi.histream.li2b2.client.Response;
import de.sekmi.histream.li2b2.client.Response.ResultStatus;
public class PMClient extends CellClient{
private static final Logger log = Logger.getLogger(PMClient.class.getName());
public static final String XMLNS = "http://www.i2b2.org/xsd/cell/pm/1.1/";
public PMClient(Client client, URL serviceUrl){
super(client, serviceUrl);
}
public void changePassword(String user, String domain, char[] oldPassword, char[] newPassword){
}
/**
* Request the configuration for the current user.
* Use this method to authenticate and retrieve available service cells.
*
* @return
* @throws HiveException
* @throws IOException
*/
public UserConfiguration requestUserConfiguration() throws HiveException, IOException{
Request req = createRequestMessage();
// set message body
//
// <pm:get_user_configuration><project>undefined</project></pm:get_user_configuration>
//
Element el = req.addBodyElement(XMLNS, "get_user_configuration");
el.appendChild(el.getOwnerDocument().createElement("project")).setTextContent("undefinded");