com.proctorcam.proctorserv.ProctorservApi.java Source code

Java tutorial

Introduction

Here is the source code for com.proctorcam.proctorserv.ProctorservApi.java

Source

//
//  ProctorservApi.java
//  ProctorservApi
//
//  Copyright 2013 ProctorCam, Inc
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
//  Created by John Tabone on 6/6/13.
//

//ProctorCam classes and exceptions
package com.proctorcam.proctorserv;

import com.proctorcam.proctorserv.exceptions.*;

//Standard Java lib
import java.util.*;
import java.lang.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

//Apache libs for HTTP POST/GET
import org.apache.commons.io.IOUtils;
import org.apache.http.*;

//GSON lib for JSON parsing 
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

public class ProctorservApi {

    private String api_identifier;
    private String shared_secret;
    private String service_url = "service.proctorcam.com";
    private String service_protocol = "https";

    /**
     * Constructs a new instance of ProctorservApi. If serviceURL and serviceProtocol are not passed in the 
     * constructor method, they are given default values of 'service.proctorcam.com' and 'https' respectively
     */
    public ProctorservApi(String api_identifier, String shared_secret, String service_url,
            String service_protocol) {
        this.api_identifier = api_identifier;
        this.shared_secret = shared_secret;
        this.service_url = service_url;
        this.service_protocol = service_protocol;
    }

    public ProctorservApi(String api_identifier, String shared_secret) {
        this.api_identifier = api_identifier;
        this.shared_secret = shared_secret;
    }

    /**
     *  Get a list of schedulable timeslots (represented as Date objects) between two 
     *  times for a given test that needs to be scheduled. This API request takes the 
     *  provided session_duration into account when comparing Proctorserv business hours. 
     *  The timeslots returned are specific for a test of duration provided.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException)  
     * 
     *  Returns (Date[]): Array of Date objects that represent schedulable timeslots in Proctorserv 
     *                    for a test of length session_duration between lower_bound and upper_bound
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - lower_bound (Date) - earlier of two timestamps to find available timeslots between
     *              - upper_bound (Date) - later of two timestamps to find available timeslots between
     *              - session_duration (int) - length in minutes alloted for this examination
     */

    public Date[] getAvailableTimeslotsBetween(HashMap<String, Object> options)
            throws IOException, InvalidClientException, InvalidIpException, InvalidSignatureException,
            MissingRequiredParameterException {
        String[] arguments = { "lower_bound", "upper_bound", "session_duration" };
        requiresOf(options, arguments);
        String url = createURL(service_protocol, service_url, "/api/scheduling/get_available_timeslots_between");
        putTimeAsInteger("lower_bound", options);
        putTimeAsInteger("upper_bound", options);

        HttpResponse response = new RestRequestClient().makeGetRequest(url, api_identifier, shared_secret, options);
        List<String> responseValues = responseToList(response);

        if (response.getStatusLine().getStatusCode() == 401)
            raiseExceptionIfNecessaryList(responseValues);

        return makeDateList(responseValues);
    }

    /**
     *  Get a list of schedulable timeslots (represented as Date objects) around the 
     *  time for a given test that needs to be scheduled. This API request takes the 
     *  provided session_duration into account when comparing Proctorserv business hours. 
     *  The timeslots returned are specific for a test of duration provided. At most, 20
     *  schedulable slots will be returned.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException)  
     * 
     *  Returns (Date[]): Array (length num_slots or 20) of Date objects that represent schedulable
     *                    timeslots in Proctorserv for a test of length session_duration around time 
     *                    passed in options
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - time (Date) - time around which to find slots 
     *              - num_slots (Date) - number of slots to return 
     *              - session_duration (int) - length in minutes alloted for this examination
     */

    public Date[] getAvailableTimeslotsAround(HashMap<String, Object> options)
            throws IOException, InvalidClientException, InvalidIpException, InvalidSignatureException,
            MissingRequiredParameterException {
        String[] arguments = { "time", "num_slots", "session_duration" };
        requiresOf(options, arguments);
        String url = createURL(service_protocol, service_url, "/api/scheduling/get_available_timeslots_around");
        putTimeAsInteger("time", options);
        HttpResponse response = new RestRequestClient().makeGetRequest(url, api_identifier, shared_secret, options);

        List<String> responseValues = responseToList(response);
        if (response.getStatusLine().getStatusCode() == 401)
            raiseExceptionIfNecessaryList(responseValues);

        return makeDateList(responseValues);
    }

    /**
     *  Makes a reservation for a session if the time passed in options is a schedulable time.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException)  
     * 
     *  Returns (int): The Proctorserv id (session_id) for the created session. Will return -1 
     *                 if time passed is not schedulable.
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - time (Date) - Time around which to find slots 
     *              - customer_subject_id (String) - Unique identifier in API customer's system 
     *                for the person taking this test
     *              - customer_client_subject_id (String) - Unique identifier in API customer's 
     *                client's system for the person taking this test
     *              - client_code (String) - Unique identifier for API customer's client 
     *              - reservation_id (String) - Unique identifier representing this test instance 
     *                in API customer's system
     *              - session_duration (int) - length in minutes alloted for this examination
     *              - exam_code (String) - Unique identifier representing the group of tests this 
     *                specific test instance belongs to
     *            Optional:
     *              - complete_url (String) - URL that the candidate is redirected to after exam completion
     *              - first_name (String) - First name of the candidate taking the exam
     *              - last_name (String) - Last name of the candidate taking the exam
     *              
     * Note: Additional parameters can be passed in options (HashMap) along with the above-mentioned parameters
     */

    public int makeReservation(HashMap<String, Object> options) throws IOException, InvalidClientException,
            InvalidIpException, InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "time", "customer_subject_id", "customer_client_subject_id", "client_code",
                "reservation_id", "session_duration", "exam_code" };
        requiresOf(options, arguments);
        String url = createURL(service_protocol, service_url, "/api/scheduling/make_reservation");
        putTimeAsInteger("time", options);
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        HashMap<String, String> responseValues = responseToMap(response);
        if (response.getStatusLine().getStatusCode() == 401)
            raiseExceptionIfNecessaryMap(responseValues);
        if (response.getStatusLine().getStatusCode() == 404)
            return -1;

        return Integer.parseInt(responseValues.get("session_id"));

    }

    /** dataDeletions
      *   Will delete all identifying data associated with a set of sessions
      *   that are scoped to the smallest possible set by any of the following parameters
      *   optional parameters:
      *     - ids (only delete from this list)
      *     - customer_subject_id (only delete sessions associated with a customer's subject id)
      *     - customer_client_subject_id (only delete sessions associated with a customer's client's subject id)
      *     - client_code (API identifier for customer client)
      *     - exam_code
      *     - lower_bound (Date Object) (seconds since unix epoch after which to delete sessions by reservation time. Defaults to 0)
      *     - upper_bound (Date Object) (seconds since unix epoch before which to delete sessions by reservation time. Defaults to max 32 bit unsigned int)
      *
      *   Note that if no parameters are passed, no sessions will be deleted.
      *
      *   returns a list of session ids that were successfully deleted 
      */
    public List<String> dataDeletions(HashMap<String, Object> options)
            throws IOException, InvalidClientException, InvalidIpException, InvalidSignatureException {
        String url = createURL(service_protocol, service_url, "/api/data_deletions/sessions");

        // API accepts Date Object so we must convert it to 
        // UTC seconds as that is what is required by the proctorserve api
        if (options.get("upper_bound") != null)
            putTimeAsInteger("upper_bound", options);
        if (options.get("lower_bound") != null)
            putTimeAsInteger("lower_bound", options);

        // Obtains reponse content
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);
        HttpEntity entity = response.getEntity();
        String responseValues = IOUtils.toString(entity.getContent());

        if (response.getStatusLine().getStatusCode() != 201) {
            Gson gson = new Gson();
            HashMap<String, String> responseMap = gson.fromJson(responseValues,
                    new TypeToken<HashMap<String, String>>() {
                    }.getType());
            raiseExceptionIfNecessaryMap(responseMap);
        }

        // parses response and cast as List
        Gson json = new Gson();
        List<String> sessionList = json.fromJson(responseValues, new TypeToken<List<String>>() {
        }.getType());

        return sessionList;
    }

    /** sessions
      *   Will return data associated with a list of sessions. 
      *   The information returned is dependant on the elements
      *   provided in the request
      *
      *   required parameters:
      *     - ids (session id's you would like to obtain data from)                        - Array of IDs (string)
      *     - elements (the type of data that you would like to retrieve for each session) - Array of elements (string)
      *
      *   optional elements:     Select one or more elements to receive data, if left blank no data will be returned
      *     - review_url         (url to proctorcam to review the past session)
      *     - video_url          (url to download the sessions video)
      *     - id_photo_url       (url to download the photo of the id provided by the sessions test taker)
      *     - headshot_photo_url (url to download the photo of test taker)
      *     - session_activity   (an array of all session events associated with the session)
      *
      *    example:
      *     - $options = array("ids" => array("393", "392"), "elements" => array("id_photo_url", "headshot_photo_url", "session_activity", "video_url"))
      */
    public HashMap<String, Object> dataRetrievals(HashMap<String, Object> options)
            throws IOException, InvalidClientException, InvalidIpException, InvalidSignatureException,
            MissingRequiredParameterException {
        String[] arguments = { "ids", "elements" };
        requiresOf(options, arguments);

        String url = createURL(service_protocol, service_url, "/api/data_retrievals/sessions");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        HashMap<String, Object> responseValues = responseToComplexMap(response);

        if (response.getStatusLine().getStatusCode() != 200) {
            raiseExceptionIfNecessaryComplexMap(responseValues);
        }

        return responseValues;
    }

    /** getAccessCode
      * Will return a object containing access code and its expiration date time based
      * on a session ID provided in the request
      * if the session id provided does not exist a error message 
      * will be returned
      *
      *  required parameters:
      *  - session_id     Integer
      */
    public Object getAccessCode(HashMap<String, Object> options) throws IOException, InvalidClientException,
            InvalidIpException, InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id" };
        requiresOf(options, arguments);

        String url = createURL(service_protocol, service_url, "/api/access_code/get_access_code");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        HashMap<String, Object> responseValues = responseToComplexMap(response);

        if (response.getStatusLine().getStatusCode() != 200) {
            raiseExceptionIfNecessaryComplexMap(responseValues);
        }

        if (responseValues.get("message") != null) {
            return responseValues;
        } else {
            return responseValues.get("access_code");
        }
    }

    /** resetAccessCode
      * Will return a string containing the new access code
      * if the session id provided does not exist a error message 
      * will be returned
      *
      *  required parameters:
      *  - session_id     Integer
      */
    public Object resetAccessCode(HashMap<String, Object> options) throws IOException, InvalidClientException,
            InvalidIpException, InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id" };
        requiresOf(options, arguments);

        String url = createURL(service_protocol, service_url, "/api/access_code/reset_access_code");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        HashMap<String, Object> responseValues = responseToComplexMap(response);

        if (response.getStatusLine().getStatusCode() != 200) {
            raiseExceptionIfNecessaryComplexMap(responseValues);
        }

        if (responseValues.get("message") != null) {
            return responseValues;
        } else {
            return responseValues.get("access_code");
        }
    }

    /**
     *  Makes a reservation for a session if now is a schedulable time.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException)  
     * 
     *  Returns (int): The Proctorserv id (session_id) for the created session. Will return null 
     *                 if time passed is not schedulable.
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - customer_subject_id (String) - Unique identifier in API customer's system 
     *                for the person taking this test
     *              - customer_client_subject_id (String) - Unique identifier in API customer's 
     *                client's system for the person taking this test
     *              - client_code (String) - Unique identifier for API customer's client
     *              - reservation_id (String) - Unique identifier representing this test instance 
     *                in API customer's system
     *              - session_duration (int) - length in minutes alloted for this examination
     *              - exam_code (String) - Unique identifier representing the group of tests this 
     *                specific test instance belongs to
     *            Optional:
     *              - complete_url (String) - URL that the candidate is redirected to after exam completion
     *              - first_name (String) - First name of the candidate taking the exam
     *              - last_name (String) - Last name of the candidate taking the exam
     *
     * Note: Additional parameters can be passed in options (HashMap) along with the above-mentioned parameters
     */

    public int makeImmediateReservation(HashMap<String, Object> options) throws IOException, InvalidClientException,
            InvalidIpException, InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "customer_subject_id", "customer_client_subject_id", "client_code", "reservation_id",
                "session_duration", "exam_code" };
        requiresOf(options, arguments);
        String url = createURL(service_protocol, service_url, "/api/scheduling/make_immediate_reservation");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        HashMap<String, String> responseValues = responseToMap(response);
        if (response.getStatusLine().getStatusCode() == 401)
            raiseExceptionIfNecessaryMap(responseValues);
        if (response.getStatusLine().getStatusCode() == 404)
            return -1;

        return Integer.parseInt(responseValues.get("session_id"));
    }

    /**
     *  Cancels a reservation for a specific session_id. A session is no longer cancelable if it 
     *  has already begun or the scheduled time has passed.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException)  
     * 
     *  Returns (Boolean): Whether or not the test was successfully canceled.
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - session_id (int) - Proctorserve id for the session to be canceled 
     */

    public Boolean cancelReservation(HashMap<String, Object> options) throws IOException, InvalidClientException,
            InvalidIpException, InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id" };
        requiresOf(options, arguments);
        String url = createURL(service_protocol, service_url, "/api/scheduling/cancel_reservation");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        if (response.getStatusLine().getStatusCode() == 204)
            return true;
        if (response.getStatusLine().getStatusCode() == 401) {
            HashMap<String, String> responseValues = responseToMap(response);
            raiseExceptionIfNecessaryMap(responseValues);
        }

        return false;
    }

    /** 
     *  Generates a secure token that can be used to grant temporary jsonp access to a session for a web browser user to take a session.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException, UnsupportedEncodingException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException) 
     *
     *  Returns (String): The JSONP token
     *   
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - session_id (int) - Proctorserve id for the session to grant access to
     *            Optional:
     *              - duration (int) - Number of minutes to grant access for (defaults to 1 hour, maximum 3 hours)
     */

    public String generateJsonpToken(HashMap<String, Object> options)
            throws UnsupportedEncodingException, InvalidClientException, InvalidIpException,
            InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id" };
        requiresOf(options, arguments);

        //Get the current time and convert to int
        Date time = new Date();
        options.put("time", time);
        putTimeAsInteger("time", options);

        if ((Integer) options.get("duration") == null) {
            options.put("duration", 60);
        } else if ((Integer) options.get("duration") > 180) {
            options.put("duration", 180);
        }

        HashedAuthenticator.applyReverseGuidAndSign(options, api_identifier, shared_secret);

        String[] token = createTokenArray(options);

        return combine(token, "%26");

    }

    /**
     *  Generate a secure token that can be used to grant temporary access to a session for a web browser user to review a session.
     *  Can be loaded an iframe or a new window to grant secure access to a single user for a specified period of time.
     *
     *  This method will raise exceptions related to authentication (InvalidClientException, 
     *  InvalidSignatureException, InvalidIpException, UnsupportedEncodingException), not providing required data 
     *  (MissingRequiredParameterException), and errors in parsing the request's response
     *  (IOException) 
     *
     *  Returns (String): The secure review URL
     *
     *  Parameters: options (HashMap) of key-value pairs for:
     *            Required:
     *              - session_id (int) - Proctorserve id for the session to grant access to
     *              - proctor_id (String)- unique identifier representing the user to grant access to. 
     *                This will determine the user's display name in Proctorserve interfaces.
     *            Optional:
     *              - duration (int) - Number of minutes to grant access for (defaults to 1 hour, maximum 3 hours)
     *              - time (Date) - Time when session event should be created. If not specified, the session event is 
     *                created immediately 
     */

    public String generateSecureReviewUrl(HashMap<String, Object> options)
            throws UnsupportedEncodingException, InvalidClientException, InvalidIpException,
            InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id", "proctor_id" };
        requiresOf(options, arguments);

        //Get the current time and convert to int
        Date time = new Date();
        options.put("time", time);
        putTimeAsInteger("time", options);

        if ((Integer) options.get("duration") == null) {
            options.put("duration", 5);
        } else if ((Integer) options.get("duration") > 180) {
            options.put("duration", 180);
        }

        HashedAuthenticator.applyReverseGuidAndSign(options, api_identifier, shared_secret);
        String[] token = createTokenArray(options);
        String token_string = combine(token, "%26");

        return service_protocol + "://" + service_url + "/review?secure_token=" + token_string + "#watch/"
                + String.valueOf(options.get("session_id"));

    }

    /** 
     *  Creates a session event that is specified by session_id (int), event_type (String), severity (int),
     *  and proctor_id (String). 
     * 
     *  Returns (Boolean): Whether or not the session event was created
     *   
     *  Parameters:
     *            Required:
     *              - session_id (int) - Proctorserve id for the session to grant access to
     *              - event_type (String) - The type of event which can be found in EventTypes.java
     *      Optional:
     *              - severity (int) - The importance-by-color of the session event (0-> grey (system event),1-> red, 
     *                2-> yellow, and 3-> green
     *              - proctor_id (String) - unique identifier representing the user to grant access to. 
     *                This will determine the user's display name in Proctorserve interfaces.
     */

    public Boolean createSessionEvent(HashMap<String, Object> options)
            throws IOException, UnsupportedEncodingException, InvalidClientException, InvalidIpException,
            InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id", "event_type" };
        requiresOf(options, arguments);

        String url = createURL(service_protocol, service_url, "/api/session_events/create");
        HttpResponse response = new RestRequestClient().makePostRequest(url, api_identifier, shared_secret,
                options);

        if (response.getStatusLine().getStatusCode() != 201) {
            HashMap<String, String> responseValues = responseToMap(response);
            raiseExceptionIfNecessaryMap(responseValues);
            return false;
        }

        return true;

    }

    /** 
    *  Retrieves a sessions launch parameters and moves the session into a 'checking_in' state
    *
    *  This method will raise exceptions related to authentication (InvalidClientException, 
    *  InvalidSignatureException, InvalidIpException, UnsupportedEncodingException), not providing required data 
    *  (MissingRequiredParameterException), and errors in parsing the request's response
    *  (IOException) 
    *
    *  Returns (String): The JSONP token
    *   
    *  Parameters: options (HashMap) of key-value pairs for:
    *            Required:
    *              - session_id (int) - Proctorserve id for the session to grant access to
    */
    public Object getLaunchParameters(HashMap<String, Object> options)
            throws IOException, UnsupportedEncodingException, InvalidClientException, InvalidIpException,
            InvalidSignatureException, MissingRequiredParameterException {
        String[] arguments = { "session_id" };
        requiresOf(options, arguments);

        String url = createURL(service_protocol, service_url, "/api/sessions/get_launch_parameters");
        HttpResponse response = new RestRequestClient().makeGetRequest(url, api_identifier, shared_secret, options);

        HashMap<String, Object> responseValues = responseToComplexMap(response);

        if (response.getStatusLine().getStatusCode() != 200) {
            raiseExceptionIfNecessaryComplexMap(responseValues);
        }

        return responseValues.get("launch_parameters");
    }

    /**
     *  Converts Date object provided by time (String) in options (HashMap) to seconds
     *  since epoch. Resulting int replaces corresponding Date object in options. 
     */

    protected void putTimeAsInteger(String time, HashMap<String, Object> options) {
        Date input = (Date) options.get(time);
        options.put(time, (Integer.toString((int) ((input.getTime()) / 1000))));
    }

    /**
     *  Checks options (HashMap) for required parameters provided in arguments (String[]).
     *
     *  Iterates through arguments to check if parameters in arguments represent keys in
     *  options. If not found, MissingRequiredParameterException is thrown and an error
     *  message is displayed. 
     */

    protected void requiresOf(HashMap<String, Object> options, String[] arguments)
            throws MissingRequiredParameterException {
        for (int i = 0; i < (arguments.length) - 1; i++) {
            if (options.containsKey(arguments[i]) == false) {
                throw new MissingRequiredParameterException(arguments[i] + " is a required parameter");
            }
        }
    }

    /**
     *  Creates method-specific URL (String) used to make a request to Proctorserve.
     */

    protected String createURL(String service_protocol, String service_url, String method) {
        StringBuilder url = new StringBuilder();
        return url.append(service_protocol).append("://").append(service_url).append("/").append(method).toString();
    }

    /**
     *  Raises exceptions if the body of a response has an error message.
     *
     *  When this function is called, the response (the first String in responseList(List)) 
     *  will be evaluated and if it matches any case an exception will be triggered
     */

    protected void raiseExceptionIfNecessaryList(List<String> responseList)
            throws InvalidClientException, InvalidSignatureException, InvalidIpException {
        if (responseList.get(0).equals("Not a valid client")) {
            throw new InvalidClientException("The api_identifier for this instance of ProctorservApi"
                    + " does not match up with any for the service.");
        } else if (responseList.get(0).equals("Signature hash is invalid")) {
            throw new InvalidSignatureException("Authentication failed for the ProctorservApi request. "
                    + "It is most likely that the shared_secret does not match up with the Proctorserve record for your API client.");
        } else if (responseList.get(0).equals("Request coming from invalid IP")) {
            throw new InvalidIpException("Authentication failed for the ProctorservApi request because "
                    + "the IP address of this machine is not whitelisted for this API client in Proctorserve.");
        }
    }

    /**
     *  Raises exceptions if the body of a response has an error message.
     *
     *  When this function is called, the response (the first String in responseMap(Map)) 
     *  will be evaluated and if it matches any case an exception will be triggered
     */
    protected void raiseExceptionIfNecessaryMap(HashMap<String, String> responseMap)
            throws InvalidClientException, InvalidSignatureException, InvalidIpException {
        if (responseMap.get("message").equals("Not a valid client")) {
            throw new InvalidClientException("The api_identifier for this instance of ProctorservApi"
                    + " does not match up with any for the service.");
        } else if (responseMap.get("message").equals("Signature hash is invalid")) {
            throw new InvalidSignatureException("Authentication failed for the ProctorservApi request. "
                    + "It is most likely that the shared_secret does not match up with the Proctorserve record for your API client.");
        } else if (responseMap.get("message").equals("Request coming from invalid IP")) {
            throw new InvalidIpException("Authentication failed for the ProctorservApi request because "
                    + "the IP address of this machine is not whitelisted for this API client in Proctorserve.");
        }
    }

    /**
     *  Raises exceptions if the body of a response has an error message.
     *
     *  When this function is called, the response (the first String in responseMap(Map)) 
     *  will be evaluated and if it matches any case an exception will be triggered
     */
    protected void raiseExceptionIfNecessaryComplexMap(HashMap<String, Object> responseMap)
            throws InvalidClientException, InvalidSignatureException, InvalidIpException {
        if (responseMap.get("message").equals("Not a valid client")) {
            throw new InvalidClientException("The api_identifier for this instance of ProctorservApi"
                    + " does not match up with any for the service.");
        } else if (responseMap.get("message").equals("Signature hash is invalid")) {
            throw new InvalidSignatureException("Authentication failed for the ProctorservApi request. "
                    + "It is most likely that the shared_secret does not match up with the Proctorserve record for your API client.");
        } else if (responseMap.get("message").equals("Request coming from invalid IP")) {
            throw new InvalidIpException("Authentication failed for the ProctorservApi request because "
                    + "the IP address of this machine is not whitelisted for this API client in Proctorserve.");
        }
    }

    /**
     *  Creates array of Date objects that serves as a return value for getAvailableTimeslotsBetween 
     *  and getAvailableTimeslotsAround.
     *
     *  This method iterates through the response (List) and uses function timeStringToDate to convert
     *  the time strings (in seconds since epoch) provided by the response into Date objects which are put in an
     *  array.
     */

    protected Date[] makeDateList(List<String> response) {
        Date[] dateList = new Date[response.size()];
        for (int i = 0; i < response.size(); i++) {
            Date timeDate = timeStringToDate(response.get(i));
            dateList[i] = timeDate;
        }
        return dateList;
    }

    /**
     *  Converts the time strings (in seconds since epoch) provided by the response into Date objects.
     *  This method is called by makeDateList.
     */

    protected Date timeStringToDate(String time) {
        int timeInt = (Integer.parseInt(time));
        long timeLong = ((long) timeInt) * 1000;
        Date timeDate = new Date(timeLong);

        return timeDate;
    }

    /**
     *  Creates a List object from the response (HttpResponse) body
     */

    protected List<String> responseToList(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        String body = IOUtils.toString(entity.getContent());
        Gson gson = new Gson();
        List<String> responseValues = gson.fromJson(body, new TypeToken<List<String>>() {
        }.getType());

        return responseValues;
    }

    /**
     *  Creates a HashMap object from the response (HttpResponse) body
     */

    protected HashMap<String, String> responseToMap(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        String body = IOUtils.toString(entity.getContent());
        Gson gson = new Gson();
        HashMap<String, String> responseValues = gson.fromJson(body, new TypeToken<HashMap<String, String>>() {
        }.getType());

        return responseValues;
    }

    protected HashMap<String, Object> responseToComplexMap(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        String body = IOUtils.toString(entity.getContent());
        Gson gson = new Gson();
        HashMap<String, Object> responseValues = gson.fromJson(body, new TypeToken<HashMap<String, Object>>() {
        }.getType());

        return responseValues;
    }

    /**
     *  Acts as a join method for arrays
     */

    protected String combine(String[] array, String glue) {
        if (array.length == 0)
            return "";
        StringBuilder template = new StringBuilder();
        int i;
        for (i = 0; i < array.length - 1; i++) {
            template.append(array[i] + glue);
        }
        return template.toString() + array[i];
    }

    /**
     *  Takes the passed HashMap and spits out an array that acts
     *  as a token template 
     */

    protected String[] createTokenArray(HashMap<String, Object> options) {
        String[] token = new String[options.size()];
        int i = 0;

        for (String key : options.keySet()) {
            String value = String.valueOf(options.get(key));
            token[i] = key + "%3D" + value;
            i++;
        }
        return token;
    }

}