com.theisleoffavalon.mcmanager.mobile.RestClient.java Source code

Java tutorial

Introduction

Here is the source code for com.theisleoffavalon.mcmanager.mobile.RestClient.java

Source

/*
 * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified copies of
 * this license document, and changing it is allowed as long as the name is
 * changed.
 * 
 * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING,
 * DISTRIBUTION AND MODIFICATION
 * 
 * 0. You just DO WHAT THE FUCK YOU WANT TO.
 */

package com.theisleoffavalon.mcmanager.mobile;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import android.util.Log;

import com.theisleoffavalon.mcmanager.mobile.MinecraftCommand.ArgType;

/**
 * This class handles talking to the server <br />
 * Many methods in this class return a Map of <String,Object>, this map contains
 * various different values that were returned from a request as there can be
 * multiple returns with different return types. The mappings for these are the
 * ones default from json-simple
 * 
 * <pre>
 *    JSON      Java
 *    string      java.lang.String
 *    number      java.lang.Number
 *    true|false   java.lang.Boolean
 *    null      null
 *    array      java.util.List
 *    object      java.util.Map
 * </pre>
 * 
 * These return types should be obvious from their names, but checked type
 * casting should be done.
 * 
 * @author Jacob Henkel
 */
public class RestClient {

    /**
     * The root URL of the API
     */
    private URL rootUrl;

    /**
     * JSONRPC version string
     */
    private static final String JSON_RPC_VERSION = "2.0";

    /**
     * Creates a rest client for the given parameters
     * 
     * @param protocol
     *            The protocol to use, can be http or https
     * @param host
     *            The host to connect to
     * @param port
     *            The port to connect to
     * @param apiRoot
     *            The root of the API on the host
     * @throws MalformedURLException
     */
    public RestClient(String protocol, String host, int port, String apiRoot) throws MalformedURLException {

        this.rootUrl = new URL(protocol, host, port, apiRoot);
        Log.d("RestClient", String.format("Rest Client created for %s", this.rootUrl.toExternalForm()));
    }

    /**
     * Sends a JSONRPC request
     * 
     * @param request
     *            The request to send
     * @return The response
     */
    private JSONObject sendJSONRPC(JSONObject request) {
        JSONObject ret = null;

        try {
            HttpURLConnection conn = (HttpURLConnection) this.rootUrl.openConnection();
            conn.setRequestMethod("POST");
            conn.setChunkedStreamingMode(0);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            OutputStreamWriter osw = new OutputStreamWriter(new BufferedOutputStream(conn.getOutputStream()));
            request.writeJSONString(osw);
            osw.close();

            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStreamReader isr = new InputStreamReader(new BufferedInputStream(conn.getInputStream()));
                ret = (JSONObject) new JSONParser().parse(isr);
            } else {
                Log.e("RestClient", String.format("Got %d instead of %d for HTTP Response", conn.getResponseCode(),
                        HttpURLConnection.HTTP_OK));
                return null;
            }

        } catch (IOException e) {
            Log.e("RestClient", "Error in sendJSONRPC", e);
        } catch (ParseException e) {
            Log.e("RestClient", "Parse return data error", e);
        }
        return ret;
    }

    /**
     * Creates a JSONRPC Object with required fields besides parameters set
     * 
     * @param method
     *            The method called
     * @return The object
     */
    @SuppressWarnings("unchecked")
    private JSONObject createJSONRPCObject(String method) {
        UUID id = UUID.randomUUID();
        JSONObject request = new JSONObject();
        request.put("jsonrpc", JSON_RPC_VERSION);
        request.put("method", method);
        request.put("id", id.toString());
        return request;
    }

    /**
     * Checks the response for errors
     * 
     * @param response
     *            The response
     * @param request
     *            The request sent for the response
     * @throws IOException
     *             If an error is encountered
     */
    private void checkJSONResponse(JSONObject response, JSONObject request) throws IOException {
        if ((response == null) || (response.get("error") != null)) {
            Log.e("RestClient", String.format("An error was encountered with the code %d with the message %s",
                    response.get("code"), response.get("message")));
            throw new IOException("Invalid response from server");
        }
        if (!response.get("id").equals(request.get("id"))) {
            Log.e("RestClient", "Response ID doesn't match!");
            throw new IOException("Got the wrong id on response");
        }
    }

    /**
     * Does a SHA-256 hash of the password
     * 
     * @param password
     *            The password to hash
     * @return The hashed password
     * @throws AuthenticationException
     *             If a problem occurs during hashing
     */
    private String hashPassword(String password) throws AuthenticationException {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(password.getBytes("UTF-8"));
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            Log.e("RestClient", "Failed to encode password", e);
            throw new AuthenticationException("Password Encode failure");
        }
    }

    /**
     * Authenticates the user with the server and gives an authentication token
     * 
     * @param user
     *            The user name to login with
     * @param password
     *            The password to use
     * @return An authentication token if successful, null otherwise
     * @throws IOException
     *             If a connection problem occurs
     * @throws AuthenticationException
     *             If a problem authenticating occurs
     */
    @SuppressWarnings("unchecked")
    public String login(String user, String password) throws IOException, AuthenticationException {
        String hashedPassword = hashPassword(password);

        Map<String, String> params = new JSONObject();
        params.put("user", user);
        params.put("password", hashedPassword);

        JSONObject request = createJSONRPCObject("login");
        request.put("params", params);
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        String auth = (String) response.get("result");

        return auth;
    }

    /**
     * Gets all available Minecraft commands on the server
     * 
     * @return A list of Minecraft commands
     * @throws IOException
     *             If an error is encountered
     */
    public Map<String, MinecraftCommand> getAllMinecraftCommands() throws IOException {
        Map<String, MinecraftCommand> cmds = new HashMap<String, MinecraftCommand>();
        JSONObject request = createJSONRPCObject("getAllCommands");
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        // Parse result
        @SuppressWarnings("unchecked")
        Map<String, JSONObject> commands = (Map<String, JSONObject>) response.get("result");
        for (String name : commands.keySet()) {
            JSONObject paramObj = commands.get(name);
            JSONArray jparams = (JSONArray) paramObj.get("params");
            JSONArray jparamTypes = (JSONArray) paramObj.get("paramTypes");

            Map<String, ArgType> params = new HashMap<String, ArgType>();
            for (int i = 0; i < jparams.size(); i++) {
                params.put((String) jparams.get(i), ArgType.getArgTypeFromString((String) jparamTypes.get(i)));
            }
            cmds.put(name, new MinecraftCommand(name, params));
        }
        return cmds;
    }

    /**
     * Gets information about the server
     * 
     * @return A map containing information about the server
     * @throws IOException
     *             If a connection problem occurs
     */
    public Map<String, Object> getServerInfo() throws IOException {
        Map<String, Object> ret = new HashMap<String, Object>();
        // Create request
        JSONObject request = createJSONRPCObject("systemInfo");
        // Send request
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        // Parse response
        @SuppressWarnings("unchecked")
        Map<String, Object> json = (JSONObject) response.get("result");
        ret.putAll(json);

        return ret;
    }

    /**
     * Executes the given command on the server the RestClient is connected to
     * 
     * @param cmd
     *            The command to execute
     * @param params
     *            The parameters to pass into the command
     * @return A map containing any return values
     * @throws IOException
     *             If a connection problem occurs
     */
    @SuppressWarnings("unchecked")
    public String executeCommand(MinecraftCommand cmd, Map<String, Object> params) throws IOException {

        // Create request
        JSONObject request = createJSONRPCObject("command");
        JSONObject command = cmd.createJSONObject(params);
        request.put("params", command);
        // Send request
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        // Parse response
        String json = (String) response.get("result");

        return json;
    }

    /**
     * This method gets a list of all mods and their versions that are currently
     * on the Minecraft server
     * 
     * @return A list of mods
     * @throws IOException
     *             If a connection problem occurs
     */
    public List<MinecraftMod> getServerMods() throws IOException {
        List<MinecraftMod> mods = new ArrayList<MinecraftMod>();

        JSONObject request = createJSONRPCObject("getMods");
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        @SuppressWarnings("unchecked")
        List<JSONObject> modlist = (List<JSONObject>) response.get("result");
        for (Map<String, String> m : modlist) {
            mods.add(new MinecraftMod(m.get("name"), m.get("version")));
        }

        return mods;

    }

    /**
     * Gets console messages since index and appends it to message
     * 
     * @param index
     *            The last index, set to -1 for all currently on server
     * @param messages
     *            The list to append to
     * @return The last index of received messages
     * @throws IOException
     *             If a connection error occurs
     */
    @SuppressWarnings("unchecked")
    public long getConsoleMessages(long index, List<String> messages) throws IOException {
        long lastIndex = index;
        if (messages == null) {
            Log.e("RestClient", "Passed in List was null!");
            throw new IllegalArgumentException("List was null");
        }
        JSONObject request = createJSONRPCObject("consoleMessages");
        request.put("params", index);
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        List<JSONObject> jmessages = (List<JSONObject>) response.get("result");
        for (JSONObject message : jmessages) {
            lastIndex = (Long) message.get("id");
            messages.add((String) message.get("message"));
        }

        return lastIndex;
    }

    /**
     * Stops the Minecraft server
     * 
     * @throws IOException
     *             If a connection problem occurs
     */
    public void stopServer() throws IOException {
        JSONObject request = createJSONRPCObject("stopServer");
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);
    }

    /**
     * Gets a list of all methods on the server's JSON-RPC service
     * 
     * @return A map of methods and their descriptions
     * @throws IOException
     *             If a connection problem occurs
     */
    public Map<String, String> getAllMethods() throws IOException {
        Map<String, String> methods = new HashMap<String, String>();
        JSONObject request = createJSONRPCObject("getAllMethods");
        JSONObject response = sendJSONRPC(request);
        checkJSONResponse(response, request);

        // Parse response
        @SuppressWarnings("unchecked")
        Map<String, String> jmethods = (Map<String, String>) response.get("result");
        for (String method : jmethods.keySet()) {
            methods.put(method, jmethods.get(method));
        }

        return methods;
    }
}