
org.brutusin.rpc.client.wskt.WebsocketEndpoint Maven / Gradle / Ivy
/*
* Copyright 2016 DREAMgenics S.L..
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.brutusin.rpc.client.wskt;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Extension;
import javax.websocket.HandshakeResponse;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.brutusin.commons.Trie;
import org.brutusin.json.ParseException;
import org.brutusin.json.spi.JsonCodec;
import org.brutusin.json.spi.JsonNode;
import org.brutusin.rpc.RpcRequest;
import org.brutusin.rpc.RpcResponse;
import org.brutusin.rpc.client.RpcCallback;
/**
*
* @author Ignacio del Valle Alles [email protected]
*/
public class WebsocketEndpoint {
private static final Logger LOGGER = Logger.getLogger(WebsocketEndpoint.class.getName());
private final URI endpoint;
private final AtomicInteger reqCounter = new AtomicInteger();
private final Map serviceMap = new HashMap();
private final Map rpcCallbacks = new HashMap();
private final Map topicCallbacks = new HashMap();
private final LinkedList reconnectingQueue = new LinkedList();
private final LinkedList> initialQueue = new LinkedList();
private final Thread pingThread;
private Websocket websocket;
private boolean reconnecting;
public WebsocketEndpoint(URI endpoint, Config cfg) {
if (cfg == null) {
cfg = new ConfigurationBuilder().build();
}
this.endpoint = endpoint;
final int pingSeconds = cfg.getPingSeconds();
doExec(new RpcCallback() {
public void call(RpcResponse response) {
if (response.getError() != null) {
LOGGER.severe(response.toString());
return;
}
JsonNode services = response.getResult();
for (int i = 0; i < services.getSize(); i++) {
JsonNode service = services.get(i);
serviceMap.put(service.get("id").asString(), service);
}
for (Trie req : initialQueue) {
exec(req.getElement1(), req.getElement2(), req.getElement3());
}
initialQueue.clear();
}
}, "rpc.wskt.services", null, true);
this.pingThread = new Thread() {
@Override
public void run() {
while (!isInterrupted()) {
try {
Thread.sleep(1000 * pingSeconds);
doExec(new RpcCallback() {
public void call(RpcResponse response) {
if (response.getError() != null) {
LOGGER.severe(response.toString());
}
}
}, "rpc.wskt.ping", null, false);
} catch (InterruptedException ie) {
break;
}
}
}
};
pingThread.setDaemon(true);
pingThread.start();
}
private synchronized void reconnect() {
if (reconnecting) {
return;
}
reconnecting = true;
if (this.websocket != null) {
try {
this.websocket.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
this.websocket.setMessageListener(null);
this.websocket = null;
}
final ClientEndpointConfig cec = new ClientEndpointConfig() {
public List getPreferredSubprotocols() {
return Collections.EMPTY_LIST;
}
public List getExtensions() {
return Collections.EMPTY_LIST;
}
public List> getEncoders() {
return Collections.EMPTY_LIST;
}
public List> getDecoders() {
return Collections.EMPTY_LIST;
}
public Map getUserProperties() {
return new HashMap();
}
public ClientEndpointConfig.Configurator getConfigurator() {
return new Configurator() {
@Override
public void beforeRequest(Map> headers) {
System.out.println(headers);
}
@Override
public void afterResponse(HandshakeResponse hr) {
super.afterResponse(hr);
}
};
}
};
final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
new Thread() {
@Override
public void run() {
try {
webSocketContainer.connectToServer(new Endpoint() {
@Override
public void onOpen(final Session session, EndpointConfig config) {
try {
synchronized (WebsocketEndpoint.this) {
WebsocketEndpoint.this.websocket = new Websocket() {
@Override
public void send(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
@Override
public void close() throws IOException {
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, null));
}
};
WebsocketEndpoint.this.websocket.setMessageListener(new MessageListener() {
@Override
public void onMessage(String message) {
try {
JsonNode response = JsonCodec.getInstance().parse(message);
if (response.get("jsonrpc") != null) {
RpcResponse rpcResponse = new RpcResponse();
if (response.get("error") != null) {
rpcResponse.setError(JsonCodec.getInstance().load(response.get("error"), RpcResponse.Error.class));
}
rpcResponse.setResult(response.get("result"));
Integer id = response.get("id").asInteger();
rpcResponse.setId(id);
RpcCallback callback = rpcCallbacks.remove(id);
callback.call(rpcResponse);
} else {
String topic = response.get("topic").asString();
TopicCallback callback = topicCallbacks.get(topic);
callback.call(response.get("message"));
}
} catch (ParseException ex) {
Logger.getLogger(WebsocketEndpoint.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
session.addMessageHandler(new MessageHandler.Whole() {
@Override
public void onMessage(String message) {
MessageListener messageListener = WebsocketEndpoint.this.websocket.getMessageListener();
if (message != null) {
messageListener.onMessage(message);
}
}
});
for (RpcRequest req : reconnectingQueue) {
sendRequest(req, true);
}
reconnectingQueue.clear();
for (String topic : topicCallbacks.keySet()) {
try {
doExec(null, "rpc.topics.subscribe", JsonCodec.getInstance().parse("{\"id\":\"" + topic + "\"}"), true);
} catch (ParseException ex) {
throw new AssertionError();
}
}
}
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
}
}
@Override
public void onClose(Session session, CloseReason closeReason) {
synchronized (WebsocketEndpoint.this) {
WebsocketEndpoint.this.websocket = null;
}
}
@Override
public void onError(Session session, Throwable thr) {
Logger.getLogger(WebsocketEndpoint.class.getName()).log(Level.SEVERE, null, thr);
}
}, cec, endpoint);
} catch (DeploymentException ex) {
LOGGER.log(Level.SEVERE, "Websocket deployment failed " + endpoint + ". " + ex.getMessage());
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
} finally {
synchronized (WebsocketEndpoint.this) {
reconnecting = false;
}
}
}
}.start();
}
private synchronized void sendRequest(RpcRequest request, boolean enqueueIfNotAvailable) {
if (this.websocket == null) {
if (enqueueIfNotAvailable) {
reconnectingQueue.add(request);
} else if (request.getId() != null) {
rpcCallbacks.remove(request.getId());
}
reconnect();
} else {
try {
this.websocket.send(JsonCodec.getInstance().transform(request));
} catch (IOException ex) {
if (!enqueueIfNotAvailable && request.getId() != null) {
rpcCallbacks.remove(request.getId());
}
LOGGER.severe(ex.getMessage());
}
}
}
private synchronized void doExec(RpcCallback callback, String serviceId, JsonNode input, boolean enqueueIfNotAvailable) {
Integer reqId = null;
if (callback != null) {
reqId = reqCounter.getAndIncrement();
rpcCallbacks.put(reqId, callback);
}
RpcRequest request = new RpcRequest();
request.setJsonrpc("2.0");
request.setId(reqId);
request.setParams(input);
request.setMethod(serviceId);
sendRequest(request, enqueueIfNotAvailable);
}
public synchronized void exec(RpcCallback callback, String serviceId, JsonNode input) {
if (serviceId == null) {
throw new IllegalArgumentException("execParam.service is required");
}
if (serviceMap.size() > 0) {
JsonNode service = serviceMap.get(serviceId);
if (service == null) {
throw new IllegalArgumentException("Service not found: '" + serviceId + "'");
}
doExec(callback, serviceId, input, true);
} else {
initialQueue.add(new Trie(callback, serviceId, input));
}
}
public synchronized void subscribe(String topicId, TopicCallback callback) {
topicCallbacks.put(topicId, callback);
if (this.websocket != null) {
try {
exec(null, "rpc.topics.subscribe", JsonCodec.getInstance().parse("{\"id\":\"" + topicId + "\"}"));
} catch (ParseException ex) {
throw new AssertionError();
}
}
}
public synchronized void unsubscribe(String topicId) {
if (!topicCallbacks.containsKey(topicId)) {
throw new IllegalArgumentException("Not subscribed to topic " + topicId);
}
try {
topicCallbacks.remove(topicId);
exec(null, "rpc.topics.unsubscribe", JsonCodec.getInstance().parse("{\"id\":\"" + topicId + "\"}"));
} catch (ParseException ex) {
throw new AssertionError();
}
}
public void close() throws IOException {
this.pingThread.interrupt();
if (this.websocket != null) {
this.websocket.close();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy