package xdi2.client.impl.websocket;
import java.io.StringWriter;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.RemoteEndpoint.Async;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xdi2.client.XDIClient;
import xdi2.client.exceptions.Xdi2ClientException;
import xdi2.client.impl.XDIAbstractClient;
import xdi2.client.impl.websocket.endpoint.WebSocketClientEndpoint;
import xdi2.client.util.URLURIUtil;
import xdi2.core.io.MimeType;
import xdi2.core.io.XDIReader;
import xdi2.core.io.XDIReaderRegistry;
import xdi2.core.io.XDIWriter;
import xdi2.core.io.XDIWriterRegistry;
import xdi2.core.syntax.XDIAddress;
import xdi2.messaging.Message;
import xdi2.messaging.MessageEnvelope;
import xdi2.messaging.response.FutureMessagingResponse;
import xdi2.messaging.response.TransportMessagingResponse;
/**
* An XDI client that can send XDI messages over WebSocket and receive results.
* It supports the following parameters (passed to the init method):
* <ul>
* <li>endpointUrl - The URL of the XDI endpoint to talk to.</li>
* <li>sendMimeType - The mime type to use to send the XDI messages to the endpoint.</li>
* </ul>
*
* @author markus
*/
public class XDIWebSocketClient extends XDIAbstractClient<FutureMessagingResponse> implements XDIClient<FutureMessagingResponse> {
public static final String KEY_ENDPOINTURI = "endpointUri";
public static final String KEY_SENDMIMETYPE = "sendmimetype";
public static final String DEFAULT_SENDMIMETYPE = "application/xdi+json;implied=0";
private static final Logger log = LoggerFactory.getLogger(XDIWebSocketClient.class);
private Session session;
private URI xdiWebSocketEndpointUri;
private MimeType sendMimeType;
private Callback callback;
private Map<XDIAddress, FutureMessagingResponse> futureMessagingResponses;
public XDIWebSocketClient(Session session, URI xdiWebSocketEndpointUri, MimeType sendMimeType) {
super();
this.session = session;
this.xdiWebSocketEndpointUri = xdiWebSocketEndpointUri;
this.sendMimeType = (sendMimeType != null) ? sendMimeType : new MimeType(DEFAULT_SENDMIMETYPE);
this.callback = null;
this.futureMessagingResponses = new HashMap<XDIAddress, FutureMessagingResponse> ();
}
public XDIWebSocketClient(Session session, URI xdiWebSocketEndpointUri) {
this(session, xdiWebSocketEndpointUri, null);
}
public XDIWebSocketClient(Session session, String xdiWebSocketEndpointUri) {
this(session, URLURIUtil.URI(xdiWebSocketEndpointUri), null);
}
public XDIWebSocketClient(Session session, Properties parameters) {
this(session, null, null);
if (parameters != null) {
if (parameters.containsKey(KEY_ENDPOINTURI)) this.xdiWebSocketEndpointUri = URLURIUtil.URI(parameters.getProperty(KEY_ENDPOINTURI));
if (parameters.containsKey(KEY_SENDMIMETYPE)) this.sendMimeType = new MimeType(parameters.getProperty(KEY_SENDMIMETYPE));
if (log.isDebugEnabled()) log.debug("Initialized with " + parameters.toString() + ".");
}
}
public XDIWebSocketClient(Session session) {
this(session, null, null);
}
public XDIWebSocketClient(URI xdiWebSocketEndpointUri, MimeType sendMimeType) {
this((Session) null, xdiWebSocketEndpointUri, sendMimeType);
}
public XDIWebSocketClient(URI xdiWebSocketEndpointUri) {
this((Session) null, xdiWebSocketEndpointUri);
}
public XDIWebSocketClient(String xdiWebSocketEndpointUri) {
this((Session) null, xdiWebSocketEndpointUri);
}
public XDIWebSocketClient(Properties parameters) {
this((Session) null, parameters);
}
public XDIWebSocketClient() {
this((Session) null);
}
@Override
protected FutureMessagingResponse sendInternal(MessageEnvelope messageEnvelope) throws Xdi2ClientException {
// find out which XDIWriter we want to use
MimeType sendMimeType = this.sendMimeType;
XDIWriter writer = XDIWriterRegistry.forMimeType(sendMimeType);
if (writer == null) {
sendMimeType = new MimeType(DEFAULT_SENDMIMETYPE);
writer = XDIWriterRegistry.forMimeType(sendMimeType);
}
if (writer == null) throw new Xdi2ClientException("Cannot find a suitable XDI writer.");
if (log.isDebugEnabled()) log.debug("Using writer " + writer.getClass().getName() + ".");
// find out which XDIReader we want to use
XDIReader reader = XDIReaderRegistry.getAuto();
if (reader == null) throw new Xdi2ClientException("Cannot find a suitable XDI reader.");
if (log.isDebugEnabled()) log.debug("Using reader " + reader.getClass().getName() + ".");
// connect
Session session = null;
try {
session = this.connect();
} catch (Exception ex) {
this.disconnect(new CloseReason(CloseCodes.PROTOCOL_ERROR, "Cannot open WebSocket connection: " + ex.getMessage()));
throw new Xdi2ClientException("Cannot open WebSocket connection: " + ex.getMessage(), ex);
}
// send the message envelope
if (log.isDebugEnabled()) log.debug("MessageEnvelope: " + messageEnvelope.getGraph().toString(null, null));
try {
Async async = session.getAsyncRemote();
StringWriter stringWriter = new StringWriter();
writer.write(messageEnvelope.getGraph(), stringWriter);
async.sendText(stringWriter.getBuffer().toString());
} catch (Exception ex) {
this.disconnect(new CloseReason(CloseCodes.PROTOCOL_ERROR, "Cannot send message envelope: " + ex.getMessage()));
throw new Xdi2ClientException("Cannot send message envelope: " + ex.getMessage(), ex);
}
// we return a future messaging response
FutureMessagingResponse futureMessagingResponse = FutureMessagingResponse.fromMessageEnvelope(messageEnvelope);
for (Message message : messageEnvelope.getMessages()) {
this.putFutureMessagingResponse(message.getContextNode().getXDIAddress(), futureMessagingResponse);
}
// done
return futureMessagingResponse;
}
@Override
public void close() {
this.disconnect(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Bye."));
}
private Session connect() throws Exception {
if (this.getSession() != null) return this.getSession();
if (this.getXdiWebSocketEndpointUri() == null) throw new Xdi2ClientException("No URL to connect to.");
// connect
if (log.isDebugEnabled()) log.debug("Connecting to " + this.getXdiWebSocketEndpointUri());
Session session = WebSocketClientEndpoint.connect(this, this.getXdiWebSocketEndpointUri()).getSession();
// done
if (log.isDebugEnabled()) log.debug("Connected successfully.");
this.setSession(session);
return session;
}
private void disconnect(CloseReason closeReason) {
try {
if (this.getSession() != null) {
if (this.getSession().isOpen()) {
this.getSession().close(closeReason);
}
}
} catch (Exception ex) {
log.error("Cannot disconnect: " + ex.getMessage(), ex);
} finally {
this.setSession(null);
}
if (log.isDebugEnabled()) log.debug("Disconnected successfully.");
}
/*
* Getters and setters
*/
public Session getSession() {
return this.session;
}
public void setSession(Session session) {
this.session = session;
}
public URI getXdiWebSocketEndpointUri() {
return this.xdiWebSocketEndpointUri;
}
public void setXdiWebSocketEndpointUri(URI xdiWebSocketEndpointUri) {
this.xdiWebSocketEndpointUri = xdiWebSocketEndpointUri;
}
public MimeType getSendMimeType() {
return this.sendMimeType;
}
public void setSendMimeType(MimeType sendMimeType) {
this.sendMimeType = sendMimeType;
}
public Callback getCallback() {
return this.callback;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
public Map<XDIAddress, FutureMessagingResponse> getFutureMessagingResponses() {
return this.futureMessagingResponses;
}
public void putFutureMessagingResponse(XDIAddress messageXDIaddress, FutureMessagingResponse futureMessagingResponse) {
if (log.isDebugEnabled()) log.debug("Putting future messaging response for message " + messageXDIaddress);
this.futureMessagingResponses.put(messageXDIaddress, futureMessagingResponse);
}
public void removeFutureMessagingResponse(XDIAddress messageXDIaddress) {
if (log.isDebugEnabled()) log.debug("Removing future messaging response for message " + messageXDIaddress);
this.futureMessagingResponses.remove(messageXDIaddress);
}
/*
* Object methods
*/
@Override
public String toString() {
return this.getXdiWebSocketEndpointUri().toString();
}
/*
* Helper classes
*/
public static interface Callback {
public void onMessageEnvelope(MessageEnvelope messageEnvelope);
public void onMessagingResponse(TransportMessagingResponse messagingResponse);
}
}