
org.tinymediamanager.jsonrpc.io.JavaConnectionManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kodi-json-rpc Show documentation
Show all versions of kodi-json-rpc Show documentation
This library is the result of freezy's Kodi JSON introspection, merged with dereulenspiegel's adoption
without android, and patched to Kodi 16 Jarvis.
The newest version!
package org.tinymediamanager.jsonrpc.io;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.jsonrpc.api.AbstractCall;
import org.tinymediamanager.jsonrpc.config.HostConfig;
import org.tinymediamanager.jsonrpc.notification.AbstractEvent;
public class JavaConnectionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(JavaConnectionManager.class);
private final List connectionListener = new ArrayList();
/**
* Since we can't return the de-serialized object from the service, put the response back into the received one and return the received one.
*/
private final Map> mCallRequests = new ConcurrentHashMap>();
private final Map> mCalls = new ConcurrentHashMap>();
private boolean isConnected = false;
private Socket socket;
private BufferedWriter bufferedWriter;
private HostConfig hostConfig;
/**
* Static reference to Jackson's object mapper.
*/
private final static ObjectMapper OM = new ObjectMapper();
/**
* Executes a JSON-RPC request with the full result in the callback.
*
* @param call
* Call to execute
* @param callback
* @return
*/
public JavaConnectionManager call(final AbstractCall call, final ApiCallback callback) {
if (isConnected) {
mCallRequests.put(call.getId(), new CallRequest(call, callback));
mCalls.put(call.getId(), call);
writeSocket(call);
}
else {
LOGGER.error("Cannot send call - NOT connected!");
}
return this;
}
public void registerConnectionListener(ConnectionListener listener) {
if (listener != null) {
connectionListener.add(listener);
}
}
public void unregisterConnectionListener(ConnectionListener listener) {
if (listener != null) {
connectionListener.remove(listener);
}
}
public boolean isConnected() {
return isConnected;
}
public void connect(HostConfig config) throws ApiException {
if (isConnected) {
disconnect();
}
this.hostConfig = config;
try {
final InetSocketAddress address = new InetSocketAddress(config.mAddress, config.mTcpPort);
socket = new Socket();
socket.setSoTimeout(0);
socket.connect(address);
bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
startParsingIncomingMessages();
isConnected = true;
notifyConnected();
}
catch (UnknownHostException e) {
disconnect();
throw new ApiException(ApiException.IO_UNKNOWN_HOST, e.getMessage(), e);
}
catch (ConnectException e) {
disconnect();
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_OPENING, e.getMessage(), e);
}
catch (IOException e) {
disconnect();
throw new ApiException(ApiException.IO_EXCEPTION, e.getMessage(), e);
}
}
public HostConfig getHostConfig() {
return hostConfig;
}
public void reconnect() throws ApiException {
connect(hostConfig);
}
private void startParsingIncomingMessages() {
new Thread() {
@Override
public void run() {
final JsonFactory jf = OM.getJsonFactory();
JsonParser jp;
try {
jp = jf.createJsonParser(socket.getInputStream());
JsonNode node;
while ((node = OM.readTree(jp)) != null) {
notifyClients(node);
}
}
catch (Exception e) {
LOGGER.warn("Error parsing incoming message: {}", e.getMessage());
disconnect();
}
};
}.start();
}
public void disconnect() {
if (isConnected) {
try {
if (bufferedWriter != null) {
bufferedWriter.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
try {
if (socket != null) {
socket.close();
}
}
catch (Exception e) {
e.printStackTrace();
}
notifyDisconnect();
isConnected = false;
}
else {
// TODO throw exception
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void notifyClients(JsonNode node) {
if (node.has("error")) {
final String id = node.get("id").getValueAsText();
if (mCallRequests.containsKey(id)) {
CallRequest callRequest = mCallRequests.remove(id);
mCalls.remove(id);
ApiCallback callback = callRequest.mCallback;
AbstractCall call = callRequest.mCall;
JsonNode errorNode = node.get("error");
int errorCode = -1;
if (errorNode.has("code"))
errorCode = errorNode.get("code").getIntValue();
String message = "";
if (errorNode.has("message"))
message = errorNode.get("message").getTextValue();
String hint = "";
if (errorNode.has("data"))
hint = errorNode.get("data").toString();
callback.onError(errorCode, message, hint);
}
else {
LOGGER.error("No such request for id {}: ERROR={}", id, node.toString());
}
// TODO
// check if notification or api call
}
else if (node.has("id")) {
// it's api call.
final String id = node.get("id").getValueAsText();
if (mCallRequests.containsKey(id)) {
CallRequest callRequest = mCallRequests.remove(id);
mCalls.remove(id);
ApiCallback callback = callRequest.mCallback;
AbstractCall call = callRequest.mCall;
call.setResponse(node);
callback.onResponse(call);
}
else {
LOGGER.error("No such request for id {}: DATA={}", id, node.toString());
}
}
else {
// it's a notification.
final AbstractEvent event = AbstractEvent.parse((ObjectNode) node);
if (event != null) {
for (ConnectionListener listener : connectionListener) {
listener.notificationReceived(event);
}
}
else {
// Log.i(TAG, "Ignoring unknown notification " +
// node.get("method").getTextValue() + ".");
}
}
}
/**
* Serializes the API request and dumps it on the socket.
*
* @param call
*/
private void writeSocket(AbstractCall> call) {
final String data = call.getRequest().toString();
LOGGER.debug("CALL: {}", data);
try {
bufferedWriter.write(data + "\n");
bufferedWriter.flush();
}
catch (IOException e) {
// TODO
}
}
/**
* A call request bundles an API call and its callback of the same type.
*
* @author freezy
*/
private static class CallRequest {
private final AbstractCall mCall;
private final ApiCallback mCallback;
public CallRequest(AbstractCall call, ApiCallback callback) {
this.mCall = call;
this.mCallback = callback;
}
public void update(AbstractCall> call) {
mCall.copyResponse(call);
}
public void respond() {
mCallback.onResponse(mCall);
}
public void error(int code, String message, String hint) {
mCallback.onError(code, message, hint);
}
}
private void notifyDisconnect() {
for (ConnectionListener listener : connectionListener) {
listener.disconnected();
}
}
private void notifyConnected() {
for (ConnectionListener listener : connectionListener) {
listener.connected();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy