/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.xbmc.rpc; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ning.http.client.AsyncCompletionHandler; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.ListenableFuture; import com.ning.http.client.Response; /** * Abstract class which all XBMC RPC calls are derived from. Handles * the posting of requests and validation/handling of responses. * * Also contains a number of helper methods for writing/parsing the * JSON request/response messages. * * XBMC JSON RPC API: http://wiki.xbmc.org/?title=JSON-RPC_API * * @author tlan, Ben Jones, Plebs * @since 1.5.0 */ public abstract class RpcCall { private static final Logger logger = LoggerFactory.getLogger(RpcCall.class); public class RpcException extends RuntimeException { private static final long serialVersionUID = 553643499122192425L; public RpcException(String message) { super(message); } public RpcException(String message, Exception e) { super(message, e); } } private final AsyncHttpClient client; private final String uri; private final ObjectMapper mapper = new ObjectMapper(); public RpcCall(AsyncHttpClient client, String uri) { this.client = client; this.uri = uri; } @SuppressWarnings("unchecked") public static Map<String, Object> getMap(Map<String, Object> data, String param) { if (!data.containsKey(param)) { return new HashMap<String, Object>(); } return (Map<String, Object>) data.get(param); } public static Integer getParamAsInteger(Map<String, Object> data, String param) { Object obj = data.get(param); if (obj != null && obj instanceof Integer) { return (Integer) obj; } return null; } public static String getParamAsString(Map<String, Object> data, String param) { Object obj = data.get(param); if (obj != null && obj instanceof String) { return (String) obj; } return null; } @SuppressWarnings("unchecked") public static List<Object> getList(Map<String, Object> data, String param) { if (!data.containsKey(param)) { return new ArrayList<Object>(); } return (List<Object>) data.get(param); } @SuppressWarnings("unchecked") private Map<String, Object> readJson(String json) { if (json == null) { return new HashMap<String, Object>(); } try { return mapper.readValue(json, Map.class); } catch (JsonParseException e) { throw new RpcException("Failed to parse JSON", e); } catch (JsonMappingException e) { throw new RpcException("Failed to map JSON", e); } catch (IOException e) { throw new RpcException("Failed to read JSON", e); } } private String writeJson(Map<String, Object> json) { try { return mapper.writeValueAsString(json); } catch (JsonParseException e) { throw new RpcException("Failed to parse JSON", e); } catch (JsonMappingException e) { throw new RpcException("Failed to map JSON", e); } catch (IOException e) { throw new RpcException("Failed to write JSON", e); } } private void postRequest(Map<String, Object> request, Runnable completeHandler) { try { // we fire this request off asynchronously and let the completeHandler // process any response as necessary (can be null) String resultWrite = writeJson(request); logger.debug("Write JSON: {}", resultWrite); ListenableFuture<Response> future = client.preparePost(uri).setBody(resultWrite) .setHeader("content-type", "application/json").setHeader("accept", "application/json") .execute(new AsyncCompletionHandler<Response>() { @Override public Response onCompleted(Response response) throws Exception { logger.debug("Read JSON: {}", response.getResponseBody()); Map<String, Object> json = readJson(response.getResponseBody()); // if we get an error then throw an exception to stop the // completion handler getting executed if (json.containsKey("error")) { throw new RpcException(json.get("error").toString()); } processResponse(json); return response; } @Override public void onThrowable(Throwable t) { logger.error("Error handling POST response from XBMC", t); } }); // add the future listener to handle the response once this request completes if (completeHandler != null) { future.addListener(completeHandler, client.getConfig().executorService()); } } catch (Exception e) { logger.error("Failed sending POST request to XBMC", e); } } protected abstract String getName(); protected abstract Map<String, Object> getParams(); protected abstract void processResponse(Map<String, Object> response); public void execute() { // nothing to do on completion execute(null); } public void execute(Runnable completeHandler) { Map<String, Object> request = new HashMap<String, Object>(); request.put("jsonrpc", "2.0"); request.put("method", getName()); request.put("id", UUID.randomUUID().toString()); Map<String, Object> params = getParams(); if (params.size() > 0) { request.put("params", params); } postRequest(request, completeHandler); } }