/* * 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.api; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonProcessingException; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.NullNode; import org.codehaus.jackson.node.ObjectNode; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; /** * Super class of all API call implementations. * * <p/> * Every sub class represents an API method of XBMC's JSON-RPC API. Basically * it implements two things: * <ol><li>Creation of the JSON request object sent to XBMC's JSON-RPC API</li> * <li>Parsing of the JSON response and serialization into our model</li></ol> * * <h3>Type</h3> * Every sub class is typed with the class of our model that is returned in the * API method. The model aims to represent the types of the JSON-RPC API. All * classes of the model extend {@link AbstractModel}. * * <h3>Lists vs. single items</h3> * API methods either return a single item or a list of items. We both define * {@link #getResult()} for a single result and {@link #getResults()} for a * bunch of results. Both work independently of what actual kind of result is * returned by XBMC. * <p/> * The difference is *what* those methods return. If the API returns a list, * {@link #getResult()} will return the first item. If the API returns a single * item, {@link #getResults()} returns a list containing only one item. * <p/> * The subclass has therefore to implement particulary two things: * <ol><li>{@link #returnsList()} returning <tt>true</tt> or <tt>false</tt> * depending on if the API returns a list or not</li> * <li>Depending on if a list is returned: * <ul><li>{@link #parseMany(JsonNode)} if that's the case, <b>or</b></li> * <li>{@link #parseOne(JsonNode)} if a single item is returned. * </li></ul> * </li></ol> * The rest is taken care of in this abstract class. * <p/> * * @author freezy <freezy@xbmc.org> */ public abstract class AbstractCall<T> implements Parcelable { // private static final String TAG = AbstractCall.class.getSimpleName(); public static final String RESULT = "result"; private final static Random RND = new Random(System.currentTimeMillis()); protected final static ObjectMapper OM = new ObjectMapper(); /** * Name of the node containing parameters in the JSON-RPC request */ private static final String PARAMS = "params"; /** * Returns the name of the method. * @return Full name of the method, e.g. "AudioLibrary.GetSongDetails". */ public abstract String getName(); /** * Returns true if the API method returns a list of items, false if the API * method returns a single item. * <p/> * Depending on this value, either {@link #parseOne(JsonNode)} or * {@link #parseMany(JsonNode)} must be overridden by the sub class. * * @return True if API call returns a list, false if only one item */ protected abstract boolean returnsList(); /** * JSON request object sent to the API * * <p/> * <u>Example</u>: * <code>{"jsonrpc": "2.0", "method": "Application.GetProperties", "id": "1", "params": { "properties": [ "version" ] } }</code> */ public ObjectNode mRequest = OM.createObjectNode(); /** * The <tt>response</tt> node of the JSON response (the root node of the * response) * <p/> * <u>Example</u>: * <code> { "version": { "major": 11, "minor": 0, "revision": "20111210-f1ae0b6", "tag": "alpha" } </code> * @todo fix example */ protected T mResult = null; protected ArrayList<T> mResults = null; /** * The ID of the request. */ private final String mId; /** * Creates the standard structure of the JSON request. * */ protected AbstractCall() { final ObjectNode request = mRequest; mId = String.valueOf(RND.nextLong()); request.put("jsonrpc", "2.0"); request.put("id", mId); request.put("method", getName()); } /** * Returns the JSON request object sent to XBMC. * @return Request object */ public ObjectNode getRequest() { return mRequest; } /** * Sets the response object once the data has arrived. * </p> * This must be the root object of the response, containing the * <tt>result</tt> object. * @param response */ public void setResponse(JsonNode response) { if (returnsList()) { mResults = parseMany(response.get(RESULT)); } else { mResult = parseOne(response.get(RESULT)); } } @SuppressWarnings("unchecked") public void copyResponse(AbstractCall<?> call) { if (returnsList()) { mResults = (ArrayList<T>)call.getResults(); } else { mResult = (T)call.getResult(); } } /** * Returns the result as a single item. * <p> * If the API method returned a list, this will return the first item, * otherwise the one item returned by the API method is returned. * * @return Result of the API method as a single item */ public T getResult() { if (returnsList()) { return mResults.get(0); } return mResult; } /** * Returns the result as a list of items. * <p> * If the API method returned a single result, this will return a list * containing the single result only, otherwise the whole list is returned. * * @return Result of the API method as list */ public ArrayList<T> getResults() { if (!returnsList()) { final ArrayList<T> results = new ArrayList<T>(1); results.add(mResult); return results; } return mResults; } /** * Returns the generated ID of the request. * @return Generated ID of the request */ public String getId() { return mId; } /** * Gets the result object from a response. * @param obj * @return */ protected JsonNode parseResult(JsonNode obj) { return obj.get(RESULT); } protected ArrayNode parseResults(JsonNode obj, String key) { if(obj.get(key) instanceof NullNode) { return null; } return (ArrayNode)obj.get(key); } /** * Parses the result if the API method returns a single item. * <p/> * Either this <b>or</b> {@link #parseMany(JsonNode)} must be overridden * by every sub class. * * @param obj The <tt>result</tt> node of the JSON response object. * @return Result of the API call */ protected T parseOne(JsonNode obj) { return null; } /** * Parses the result if the API method returns a list of items. * <p/> * Either this <b>or</b> {@link #parseOne(JsonNode)} must be overridden * by every sub class. * * @param obj The <tt>result</tt> node of the JSON response object. * @return Result of the API call */ protected ArrayList<T> parseMany(JsonNode obj) { return null; } /** * Adds a string parameter to the request object (only if not null). * @param name Name of the parameter * @param value Value of the parameter */ protected void addParameter(String name, String value) { if (value != null) { getParameters().put(name, value); } } /** * Adds an integer parameter to the request object (only if not null). * @param name Name of the parameter * @param value Value of the parameter */ protected void addParameter(String name, Integer value) { if (value != null) { getParameters().put(name, value); } } /** * Adds a boolean parameter to the request object (only if not null). * @param name Name of the parameter * @param value Value of the parameter */ protected void addParameter(String name, Boolean value) { if (value != null) { getParameters().put(name, value); } } protected void addParameter(String name, Double value) { if (value != null) { getParameters().put(name, value); } } protected void addParameter(String name, AbstractModel value) { if (value != null) { getParameters().put(name, value.toJsonNode()); } } /** * Adds an array of strings to the request object (only if not null and not empty). * @param name Name of the parameter * @param values String values */ protected void addParameter(String name, String[] values) { // don't add if nothing to add if (values == null || values.length == 0) { return; } final ArrayNode props = OM.createArrayNode(); for (int i = 0; i < values.length; i++) { props.add(values[i]); } getParameters().put(name, props); } /** * Adds a hashmap of strings to the request object (only if not null and not empty). * @param name Name of the parmeter * @param map String map */ protected void addParameter(String name, HashMap<String, String> map) { if (map == null || map.size() == 0) { return; } final ObjectNode props = OM.createObjectNode(); for (String key : map.values()) { props.put(key, map.get(key)); } getParameters().put(name, props); } /** * Returns the parameters array. Use this to add any parameters. * @param request * @return */ private ObjectNode getParameters() { final ObjectNode request = mRequest; if (request.has(PARAMS)) { return (ObjectNode)request.get(PARAMS); } else { final ObjectNode parameters = OM.createObjectNode(); request.put(PARAMS, parameters); return parameters; } } /** * Flatten this object into a Parcel. * @param parcel the Parcel in which the object should be written * @param flags additional flags about how the object should be written */ @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mId); parcel.writeValue(mRequest.toString()); } @Override public int describeContents() { return 0; } protected AbstractCall(Parcel parcel) { mId = parcel.readString(); try { mRequest = (ObjectNode)OM.readTree(parcel.readString()); } catch (JsonProcessingException e) { Log.e(getName(), "Error reading JSON object from parcel: " + e.getMessage(), e); } catch (IOException e) { Log.e(getName(), "I/O exception reading JSON object from parcel: " + e.getMessage(), e); } } }