Java tutorial
/* * Copyright (C) 2005-2010 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.jsonrpc; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import org.apache.http.HttpException; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ObjectNode; import org.xbmc.android.util.Base64; import org.xbmc.api.business.INotifiableManager; import org.xbmc.api.object.Host; import org.xbmc.httpapi.NoSettingsException; import org.xbmc.jsonrpc.client.Client; import android.util.Log; /** * Singleton class. Will be instantiated only once and contains mostly help * * @author Team XBMC */ public class Connection { private static final String TAG = "Connection-JsonRpc"; private static final String XBMC_JSONRPC_BOOTSTRAP = "/jsonrpc"; private static final int SOCKET_CONNECTION_TIMEOUT = 5000; /** * Singleton class instance */ private static Connection sConnection; /** * Complete URL without any attached command parameters, for instance: * <code>http://192.168.0.10:8080</code> */ private String mUrlSuffix;; /** * Socket read timeout (connection timeout is default) */ private int mSocketReadTimeout = 0; /** * Holds the base64 encoded user/pass for http authentication */ private String authEncoded = null; /** * Use getInstance() for public class instantiation * @param host XBMC host * @param port Port */ private Connection(String host, int port) { setHost(host, port); } /** * Returns the singleton instance of this connection. Note that host and * port settings are only looked at the first time. Use {@link setHost()} * if you want to update these parameters. * @param host XBMC host * @param port HTTP API / JSON-RPC port (it's the same) * @return Connection instance */ public static Connection getInstance(String host, int port) { if (sConnection == null) { sConnection = new Connection(host, port); } if (sConnection.mUrlSuffix == null) { sConnection.setHost(host, port); } return sConnection; } /** * Updates host info of the connection instance * @param host */ public void setHost(Host host) { if (host == null) { setHost(null, 0); } else { setHost(host.addr, host.port); setAuth(host.user, host.pass); } } /** * Updates host and port parameters of the connection instance. * @param host Host or IP address of the host * @param port HTTP port */ public void setHost(String host, int port) { if (host == null || port <= 0) { mUrlSuffix = null; } else { StringBuilder sb = new StringBuilder(); sb.append("http://"); sb.append(host); sb.append(":"); sb.append(port); mUrlSuffix = sb.toString(); } } /** * Sets authentication info * @param user Username * @param pass Password */ public void setAuth(String user, String pass) { if (user != null && pass != null) { String auth = user + ":" + pass; authEncoded = Base64.encodeBytes(auth.getBytes()).toString(); } else { authEncoded = null; } } /** * Sets socket read timeout (connection timeout has constant value) * @param timeout Read timeout in milliseconds. */ public void setTimeout(int timeout) { if (timeout > 0) { mSocketReadTimeout = timeout; } } public InputStream getThumbInputStream(String thumb, INotifiableManager manager) throws FileNotFoundException { URLConnection uc = null; try { if (mUrlSuffix == null) { throw new NoSettingsException(); } URL url = new URL(thumb); Log.i(TAG, "Preparing input stream from " + thumb + " for microhttpd.."); uc = getUrlConnection(url); return uc.getInputStream(); } catch (FileNotFoundException e) { throw e; } catch (MalformedURLException e) { manager.onError(e); } catch (IOException e) { manager.onError(e); } catch (NoSettingsException e) { manager.onError(e); } return null; } /** * Returns the full URL of an HTTP API request * @param command Name of the command to execute * @param parameters Parameters, separated by ";". * @return Absolute URL to HTTP API */ public String getUrl(String path) { // create url StringBuilder sb = new StringBuilder(mUrlSuffix); sb.append("/"); sb.append(path); return sb.toString(); } public byte[] download(String pathToDownload) throws IOException, URISyntaxException { try { final URL url = new URL(pathToDownload); final URLConnection uc = getUrlConnection(url); final InputStream is = uc.getInputStream(); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] data = new byte[8192]; int nRead; while ((nRead = is.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } return buffer.toByteArray(); } catch (Exception e) { return null; } } /** * Create a new URLConnection with the request headers set, including authentication. * * @param url The request url * @return URLConnection * @throws IOException */ private URLConnection getUrlConnection(URL url) throws IOException { final URLConnection uc = url.openConnection(); uc.setConnectTimeout(SOCKET_CONNECTION_TIMEOUT); uc.setReadTimeout(mSocketReadTimeout); uc.setRequestProperty("Connection", "close"); if (authEncoded != null) { uc.setRequestProperty("Authorization", "Basic " + authEncoded); } return uc; } /** * Executes a query. * @param command Name of the command to execute * @param parameters Parameters * @param manager Reference back to business layer * @return Parsed JSON object, empty object on error. */ public JsonNode query(String command, JsonNode parameters, INotifiableManager manager) { URLConnection uc = null; try { final ObjectMapper mapper = Client.MAPPER; if (mUrlSuffix == null) { throw new NoSettingsException(); } final URL url = new URL(mUrlSuffix + XBMC_JSONRPC_BOOTSTRAP); uc = url.openConnection(); uc.setConnectTimeout(SOCKET_CONNECTION_TIMEOUT); uc.setReadTimeout(mSocketReadTimeout); if (authEncoded != null) { uc.setRequestProperty("Authorization", "Basic " + authEncoded); } uc.setRequestProperty("Content-Type", "application/json"); uc.setDoOutput(true); final ObjectNode data = Client.obj().p("jsonrpc", "2.0").p("method", command).p("id", "1"); if (parameters != null) { data.put("params", parameters); } final JsonFactory jsonFactory = new JsonFactory(); final JsonGenerator jg = jsonFactory.createJsonGenerator(uc.getOutputStream(), JsonEncoding.UTF8); jg.setCodec(mapper); // POST data jg.writeTree(data); jg.flush(); final JsonParser jp = jsonFactory.createJsonParser(uc.getInputStream()); jp.setCodec(mapper); final JsonNode ret = jp.readValueAs(JsonNode.class); return ret; } catch (MalformedURLException e) { manager.onError(e); } catch (IOException e) { int responseCode = -1; try { responseCode = ((HttpURLConnection) uc).getResponseCode(); } catch (IOException e1) { } // do nothing, getResponse code failed so treat as default i/o exception. if (uc != null && responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { manager.onError(new HttpException(Integer.toString(HttpURLConnection.HTTP_UNAUTHORIZED))); } else { manager.onError(e); } } catch (NoSettingsException e) { manager.onError(e); } return new ObjectNode(null); } /** * Executes a JSON-RPC command and returns the result as JSON object. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param parameters Parameters of the method * @return Result */ public JsonNode getJson(INotifiableManager manager, String method, JsonNode parameters) { try { final JsonNode response = query(method, parameters, manager); final JsonNode result = response.get(RESULT_FIELD); if (result == null) { if (response.get(ERROR_FIELD) == null) { throw new Exception("Weird JSON response, could not parse error."); } else if (!response.get(ERROR_FIELD).get("message").getTextValue().equals("Invalid params.")) { throw new Exception(response.get(ERROR_FIELD).get("message").getTextValue()); } else { Log.d(TAG, "Request returned Invalid Params."); } } else { return response.get(RESULT_FIELD); } } catch (Exception e) { manager.onError(e); } return Client.obj(); } public JsonNode getJson(INotifiableManager manager, String method, JsonNode parameters, String resultField) { try { final JsonNode response = getJson(manager, method, parameters); final JsonNode result = response.get(resultField); if (result == null) { throw new Exception("Could not find field \"" + resultField + "\" as return value."); } else { return response.get(resultField); } } catch (Exception e) { manager.onError(e); } return Client.obj(); } /** * Executes a JSON-RPC command without parameters and returns the result as * JSON object. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @return Result */ public JsonNode getJson(INotifiableManager manager, String method) { return query(method, null, manager).get(RESULT_FIELD); } /** * Executes an JSON-RPC method and returns the result from a field as string. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @param returnField Name of the field to return * @return Result */ public String getString(INotifiableManager manager, String method, ObjectNode parameters, String returnField) { final JsonNode result = (JsonNode) query(method, parameters, manager).get(RESULT_FIELD); if (returnField == null) return result == null ? "" : result.getValueAsText(); else return result == null ? "" : result.get(returnField).getValueAsText(); } public String getString(INotifiableManager manager, String method, ObjectNode parameters) { return getString(manager, method, parameters, null); } /** * Executes an JSON-RPC method and returns the result from a field as integer. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @param returnField Name of the field to return * @return Result as integer */ public int getInt(INotifiableManager manager, String method, ObjectNode parameters, String returnField) { try { return Integer.parseInt(getString(manager, method, parameters, returnField)); } catch (NumberFormatException e) { return 0; } } /** * Executes an JSON-RPC method without parameter and returns the result * from a field as integer. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param returnField Name of the field to return * @return Result as integer */ public int getInt(INotifiableManager manager, String method, String returnField) { return getInt(manager, method, null, returnField); } /** * Executes an JSON-RPC method and returns the result from a field as boolean. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param parameters Parameters of the method, separated by ";" * @param returnField Name of the field to return * @return Result as boolean */ public boolean getBoolean(INotifiableManager manager, String method, ObjectNode parameters, String returnField) { return getString(manager, method, parameters, returnField).equals("true"); } /** * Executes an JSON-RPC method without parameters and returns the result * from a field as boolean. * @param manager Upper layer reference for error posting * @param method Name of the method to run * @param returnField Name of the field to return * @return Result as boolean */ public boolean getBoolean(INotifiableManager manager, String method, ObjectNode parameters) { return getBoolean(manager, method, parameters, null); } /** * HTTP Authenticator. * @author Team XBMC */ public class HttpAuthenticator extends Authenticator { public static final int MAX_RETRY = 5; private final String mUser; private final char[] mPass; private int mRetryCount = 0; public HttpAuthenticator(String user, String pass) { mUser = user; mPass = pass != null ? pass.toCharArray() : new char[0]; } /** * This method is called when a password-protected URL is accessed */ protected PasswordAuthentication getPasswordAuthentication() { if (mRetryCount < MAX_RETRY) { mRetryCount++; return new PasswordAuthentication(mUser, mPass); } return null; } /** * This method has to be called after each successful connection!!! */ public void resetCounter() { mRetryCount = 0; } } public static final String RESULT_FIELD = "result"; public static final String ERROR_FIELD = "error"; }