/*
* Copyright (C) 2005-2015 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC Remote; see the file license. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
package org.xbmc.android.jsonrpc.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import android.util.Log;
/**
* Performs HTTP POST requests on the XBMC JSON API and handles the parsing from and to {@link ObjectNode}.
*
* @author Joel Stemmer <stemmertech@gmail.com>
*/
public class JsonApiRequest {
private static final String TAG = JsonApiRequest.class.getSimpleName();
private static final int REQUEST_TIMEOUT = 5000; // 5 sec
private static final ObjectMapper OM = new ObjectMapper();
/**
* Execute a POST request to the url using the JSON Object as request body
* and returns a JSONO bject if the response was successful.
*
* @param url
* @param entity
* @return JSON Object of the JSON-RPC response.
* @throws HandlerException
*/
public static ObjectNode execute(String url, ObjectNode entity) throws ApiException {
try {
String response = postRequest(new URL(url), entity.toString());
return parseResponse(response);
} catch(MalformedURLException e) {
throw new ApiException(ApiException.MALFORMED_URL, e.getMessage(), e);
}
}
/**
* Execute a POST request on URL using entity as request body.
*
* @param url
* @param entity
* @return The response as a string
* @throws HandlerException
* @throws IOException
*/
private static String postRequest(URL url, String entity) throws ApiException {
try {
final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("User-Agent", buildUserAgent());
conn.setConnectTimeout(REQUEST_TIMEOUT);
conn.setReadTimeout(REQUEST_TIMEOUT);
conn.setDoOutput(true);
try {
OutputStreamWriter output = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
output.write(entity);
output.close();
} catch(UnsupportedEncodingException e) {
throw new ApiException(ApiException.UNSUPPORTED_ENCODING, "Unable to convert request to UTF-8", e);
}
Log.i(TAG, "POST request: " + conn.getURL());
Log.i(TAG, "POST entity:" + entity);
StringBuilder response = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"), 8192);
String line;
while((line = reader.readLine()) != null) {
response.append(line);
}
} catch(UnsupportedEncodingException e) {
throw new ApiException(ApiException.UNSUPPORTED_ENCODING, "Unable to convert HTTP response to UTF-8", e);
} finally {
if (reader != null) {
reader.close();
}
}
Log.i(TAG, "POST response: " + response.toString());
return response.toString();
} catch (SocketTimeoutException e) {
throw new ApiException(ApiException.IO_SOCKETTIMEOUT, e.getMessage(), e);
} catch (IOException e) {
throw new ApiException(ApiException.IO_EXCEPTION, e.getMessage(), e);
}
}
/**
* Parses the JSON response string and returns a {@link ObjectNode}.
*
* If the response is not valid JSON, contained an error message or did not include a result then a HandlerException
* is thrown.
*
* @param response
* @return ObjectNode Root node of the server response, unserialized as ObjectNode.
* @throws HandlerException
*/
private static ObjectNode parseResponse(String response) throws ApiException {
try {
final ObjectNode node = (ObjectNode)OM.readTree(response.toString());
if (node.has("error")) {
final ObjectNode error = (ObjectNode)node.get("error");
Log.e(TAG, "[JSON-RPC] " + error.get("message").getTextValue());
Log.e(TAG, "[JSON-RPC] " + response);
throw new ApiException(ApiException.API_ERROR, "Error " + error.get("code").getIntValue() + ": " + error.get("message").getTextValue(), null);
}
if (!node.has("result")) {
Log.e(TAG, "[JSON-RPC] " + response);
throw new ApiException(ApiException.RESPONSE_ERROR, "Neither result nor error object found in response.", null);
}
if (node.get("result").isNull()) {
return null;
}
return node;
} catch (JsonProcessingException e) {
throw new ApiException(ApiException.JSON_EXCEPTION, "Parse error: " + e.getMessage(), e);
} catch (IOException e) {
throw new ApiException(ApiException.JSON_EXCEPTION, "Parse error: " + e.getMessage(), e);
}
}
/**
* Build user agent used for the HTTP requests
* TODO: include version information
*
* @return String containing the user agent
*/
private static String buildUserAgent() {
return "XBMCRemote (1.0)";
}
}