ch.gianulli.trelloapi.TrelloAPI.java Source code

Java tutorial

Introduction

Here is the source code for ch.gianulli.trelloapi.TrelloAPI.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Gian Ulli (gian.ulli@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package ch.gianulli.trelloapi;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.util.Log;

import com.android.volley.NetworkError;
import com.android.volley.NoConnectionError;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.RequestFuture;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import ch.gianulli.trelloapi.ui.AuthenticationDialogFragment;

/**
 * Base class that maps the Trello API. It uses <a href="https://developer.android
 * .com/training/volley/index.html">Volley</a> for network requests.
 */
public class TrelloAPI {

    private static final String PREF_NAME = "trello_api";

    /**
     * Used for storing the access token
     */
    private static final String PREF_KEY_TOKEN = "token";

    /**
     * Name of metadata field in manifest that contains the application key
     */
    private static final String META_DATA_APP_KEY = "ch.gianulli.trelloapi.APP_KEY";

    /**
     * Name of metadata field in manifest that contains the application secret
     */
    private static final String META_DATA_APP_SECRET = "ch.gianulli.trelloapi.APP_SECRET";

    private static final String BASE_URL = "https://trello.com/1/";

    private Context mContext;

    private String mAppKey;

    private String mAppSecret;

    private SharedPreferences mPreferences;

    private String mToken;

    private RequestQueue mRequestQueue;

    /**
     * @param context Current context (e.g. activity)
     * @throws IllegalArgumentException if the application key and secret are not in a {@code
     *                                  <meta-data>} tag inside the {@code <application>} tag in
     *                                  the app's manifest.
     */
    public TrelloAPI(Context context) {
        mContext = context;
        mPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        mRequestQueue = Volley.newRequestQueue(context);

        // Get application key from <meta-data> tag in manifest
        try {
            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(),
                    PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;

            if (!bundle.containsKey(META_DATA_APP_KEY)) {
                throw new IllegalArgumentException(
                        "Application key could not be found. Have you" + " put it in your application manifest?");
            }
            if (!bundle.containsKey(META_DATA_APP_SECRET)) {
                throw new IllegalArgumentException("Application secret could not be found. Have "
                        + "you put it in your application manifest?");
            }

            mAppKey = bundle.getString(META_DATA_APP_KEY);
            mAppSecret = bundle.getString(META_DATA_APP_SECRET);
        } catch (PackageManager.NameNotFoundException e) {
            throw new IllegalArgumentException("Application key and secret could not be found. "
                    + "Have you put them in your application manifest?");
        }
    }

    /**
     * Opens a dialog that handles the user authorization
     *
     * @param fragmentManager Support fragment manager
     */
    public void requestAuthorization(FragmentManager fragmentManager) {
        Log.d("TrelloAPI", "requestAuthorization");
        AuthenticationDialogFragment fragment = AuthenticationDialogFragment.newInstance();
        fragment.show(fragmentManager, "dialog");
    }

    /**
     * Attention: this method makes a synchronous network request!
     *
     * @return true if stored token is valid, false otherwise
     */
    public boolean validateToken() throws TrelloNotAccessibleException {
        try {
            makeStringRequest("GET", "members/me", null, true);
        } catch (TrelloNotAuthorizedException e) {
            return false;
        }
        return true;
    }

    /**
     * Utility method to make Trello API requests
     *
     * @param httpMethod       Either GET, POST, PUT or DELETE
     * @param path             e.g. "actions/[idAction]"
     * @param queryArgs        query arguments
     * @param isTokenNecessary is access token necessary?
     * @return server answer
     * @throws TrelloNotAccessibleException if Trello API is not accessible
     * @throws TrelloNotAuthorizedException if token is not valid
     * @throws MalformedURLException        if path was not correctly formatted
     */
    protected JSONArray makeJSONArrayRequest(String httpMethod, String path, Map<String, String> queryArgs,
            boolean isTokenNecessary) throws TrelloNotAccessibleException, TrelloNotAuthorizedException {
        // Add key and token to arguments
        if (queryArgs == null) {
            queryArgs = new LinkedHashMap<>();
        }
        queryArgs.put("key", getAppKey());
        if (isTokenNecessary) {
            queryArgs.put("token", getToken());
        }

        // Build argument string
        StringBuilder getData = new StringBuilder();
        try {
            for (Map.Entry<String, String> param : queryArgs.entrySet()) {
                if (getData.length() != 0) {
                    getData.append('&');
                }
                getData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
                getData.append('=');
                getData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            // never happens
        }

        // Check if httpMethod is supported
        int method = -1;
        if (httpMethod.equals("GET")) {
            method = Request.Method.GET;
        } else if (httpMethod.equals("POST")) {
            method = Request.Method.POST;
        } else if (httpMethod.equals("PUT")) {
            method = Request.Method.PUT;
        } else if (httpMethod.equals("DELETE")) {
            method = Request.Method.DELETE;
        } else {
            throw new IllegalArgumentException("HTTP method not supported: " + httpMethod);
        }

        String url = BASE_URL + path + "?" + getData.toString();

        try {
            RequestFuture<JSONArray> future = RequestFuture.newFuture();
            JsonArrayRequest request = new JsonArrayRequest(method, url, null, future, future);
            mRequestQueue.add(request);

            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new TrelloNotAccessibleException("Network request was interrupted.");
        } catch (ExecutionException e) {
            VolleyError ve = (VolleyError) e.getCause();
            if (ve instanceof NoConnectionError || ve instanceof NetworkError) {
                throw new TrelloNotAccessibleException("Device is not connected to the internet.");
            } else if (ve instanceof ParseError) {
                throw new TrelloNotAccessibleException("Server answer was not in valid JSON format" + ".");
            } else if (ve.networkResponse != null) {
                if (ve.networkResponse.statusCode == 401 || ve.networkResponse.statusCode == 400) {
                    throw new TrelloNotAuthorizedException("Server returned error 401");
                } else {
                    throw new TrelloNotAccessibleException("Server returned error " + ve.networkResponse.statusCode
                            + ": " + new String(ve.networkResponse.data));
                }
            } else {
                Log.e("Flashcards for Trello", "An unknown exception was thrown.", e);
                e.printStackTrace();
            }
        } catch (TimeoutException e) {
            throw new TrelloNotAccessibleException("Network request timed out");
        }

        return null;
    }

    /**
     * Utility method to make Trello API requests
     *
     * @param httpMethod       Either GET, POST, PUT or DELETE
     * @param path             e.g. "actions/[idAction]"
     * @param queryArgs        query arguments
     * @param isTokenNecessary is access token necessary?
     * @return server answer
     * @throws TrelloNotAccessibleException if Trello API is not accessible
     * @throws TrelloNotAuthorizedException if token is not valid
     * @throws MalformedURLException        if path was not correctly formatted
     */
    protected JSONObject makeJSONObjectRequest(String httpMethod, String path, Map<String, String> queryArgs,
            boolean isTokenNecessary) throws TrelloNotAccessibleException, TrelloNotAuthorizedException {
        // Add key and token to arguments
        if (queryArgs == null) {
            queryArgs = new LinkedHashMap<>();
        }
        queryArgs.put("key", getAppKey());
        if (isTokenNecessary) {
            queryArgs.put("token", getToken());
        }

        // Build argument string
        StringBuilder getData = new StringBuilder();
        try {
            for (Map.Entry<String, String> param : queryArgs.entrySet()) {
                if (getData.length() != 0) {
                    getData.append('&');
                }
                getData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
                getData.append('=');
                getData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            // never happens
        }

        // Check if httpMethod is supported
        int method = -1;
        if (httpMethod.equals("GET")) {
            method = Request.Method.GET;
        } else if (httpMethod.equals("POST")) {
            method = Request.Method.POST;
        } else if (httpMethod.equals("PUT")) {
            method = Request.Method.PUT;
        } else if (httpMethod.equals("DELETE")) {
            method = Request.Method.DELETE;
        } else {
            throw new IllegalArgumentException("HTTP method not supported: " + httpMethod);
        }

        String url = BASE_URL + path + "?" + getData.toString();

        try {
            RequestFuture<JSONObject> future = RequestFuture.newFuture();
            JsonObjectRequest request = new JsonObjectRequest(method, url, null, future, future);
            mRequestQueue.add(request);

            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new TrelloNotAccessibleException("Network request was interrupted.");
        } catch (ExecutionException e) {
            VolleyError ve = (VolleyError) e.getCause();
            if (ve instanceof NoConnectionError || ve instanceof NetworkError) {
                throw new TrelloNotAccessibleException("Device is not connected to the internet.");
            } else if (ve instanceof ParseError) {
                throw new TrelloNotAccessibleException("Server answer was not in valid JSON format" + ".");
            } else if (ve.networkResponse != null) {
                if (ve.networkResponse.statusCode == 401) {
                    throw new TrelloNotAuthorizedException("Server returned error 401");
                } else {
                    throw new TrelloNotAccessibleException("Server returned error " + ve.networkResponse.statusCode
                            + ": " + new String(ve.networkResponse.data));
                }
            } else {
                Log.e("Flashcards for Trello", "An unknown exception was thrown.", e);
                e.printStackTrace();
            }
        } catch (TimeoutException e) {
            throw new TrelloNotAccessibleException("Network request timed out");
        }

        return null;
    }

    /**
     * Utility method to make Trello API requests
     *
     * @param httpMethod       Either GET, POST, PUT or DELETE
     * @param path             e.g. "actions/[idAction]"
     * @param queryArgs        query arguments
     * @param isTokenNecessary is access token necessary?
     * @return server answer
     * @throws TrelloNotAccessibleException if Trello API is not accessible
     * @throws TrelloNotAuthorizedException if token is not valid
     * @throws MalformedURLException        if path was not correctly formatted
     */
    protected String makeStringRequest(String httpMethod, String path, Map<String, String> queryArgs,
            boolean isTokenNecessary) throws TrelloNotAccessibleException, TrelloNotAuthorizedException {
        // Add key and token to arguments
        if (queryArgs == null) {
            queryArgs = new LinkedHashMap<>();
        }
        queryArgs.put("key", getAppKey());
        if (isTokenNecessary) {
            queryArgs.put("token", getToken());
        }

        // Build argument string
        StringBuilder getData = new StringBuilder();
        try {
            for (Map.Entry<String, String> param : queryArgs.entrySet()) {
                if (getData.length() != 0) {
                    getData.append('&');
                }
                getData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
                getData.append('=');
                getData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            // never happens
        }

        // Check if httpMethod is supported
        int method = -1;
        if (httpMethod.equals("GET")) {
            method = Request.Method.GET;
        } else if (httpMethod.equals("POST")) {
            method = Request.Method.POST;
        } else if (httpMethod.equals("PUT")) {
            method = Request.Method.PUT;
        } else if (httpMethod.equals("DELETE")) {
            method = Request.Method.DELETE;
        } else {
            throw new IllegalArgumentException("HTTP method not supported: " + httpMethod);
        }

        String url = BASE_URL + path + "?" + getData.toString();

        try {
            RequestFuture<String> future = RequestFuture.newFuture();
            StringRequest request = new StringRequest(method, url, future, future);
            mRequestQueue.add(request);

            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new TrelloNotAccessibleException("Network request was interrupted.");
        } catch (ExecutionException e) {
            VolleyError ve = (VolleyError) e.getCause();
            if (ve != null && ve.networkResponse != null) {
                if (ve.networkResponse.statusCode == 401) {
                    throw new TrelloNotAuthorizedException("Server returned error 401");
                } else {
                    throw new TrelloNotAccessibleException("Server returned error " + ve.networkResponse.statusCode
                            + ": " + new String(ve.networkResponse.data));
                }
            }
        } catch (TimeoutException e) {
            throw new TrelloNotAccessibleException("Network request timed out");
        }

        return null;
    }

    /**
     * @return application key
     */
    public String getAppKey() {
        return mAppKey;
    }

    /**
     * @return application secret
     */
    public String getAppSecret() {
        return mAppSecret;
    }

    /**
     * @return Access token or null, if no token is stored.
     */
    public String getToken() {
        if (mToken == null) {
            mToken = mPreferences.getString(PREF_KEY_TOKEN, null);
        }
        return mToken;
    }

    public void setToken(String token) {
        mToken = token;
        mPreferences.edit().putString(PREF_KEY_TOKEN, token).apply();
    }

    public Context getContext() {
        return mContext;
    }

    public void setContext(Context context) {
        mContext = context;
    }
}