org.eyeseetea.malariacare.network.ServerAPIController.java Source code

Java tutorial

Introduction

Here is the source code for org.eyeseetea.malariacare.network.ServerAPIController.java

Source

/*
 * Copyright (c) 2015.
 *
 * This file is part of QIS Surveillance App.
 *
 *  QIS Surveillance App 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  QIS Surveillance App 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 QIS Surveillance App.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.eyeseetea.malariacare.network;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;

import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import org.eyeseetea.malariacare.R;
import org.eyeseetea.malariacare.database.model.Program;
import org.eyeseetea.malariacare.database.model.Survey;
import org.eyeseetea.malariacare.database.utils.PreferencesState;
import org.eyeseetea.malariacare.utils.Constants;
import org.eyeseetea.malariacare.utils.Utils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.Proxy;
import java.util.Calendar;
import java.util.List;

/**
 * Utility class that shows specific operations to check server status with the given config
 * Created by arrizabalaga on 28/01/16.
 */
public class ServerAPIController {

    private static final String TAG = "ServerAPIController";

    /**
     * Hardcoded username via API
     */
    static final String DHIS_USERNAME = "KHMCS";

    /**
     * Hardcoded password via API
     */
    static final String DHIS_PASSWORD = "KHMCSadmin1";

    /**
     * Tag for version data in json response
     */
    public static final String TAG_VERSION = "version";

    /**
     * Tag for id (program) in json response
     */
    private static final String TAG_ID = "id";

    /**
     * Tag for closedDate (orgUnit) in json response
     */
    private static final String TAG_CLOSEDDATE = "closedDate";

    /**
     * Tag for orgunit description in json request/response
     */
    private static final String TAG_DESCRIPTIONCLOSEDATE = "description";

    /**
     * Tag for organisationUnits in json response
     */
    private static final String TAG_ORGANISATIONUNITS = "organisationUnits";

    /**
     * Tag for code attribute in orgUnits (json)
     */
    private static final String TAG_CODE = "code";

    /**
     * Date format to the closedDate attribute
     */
    private static final String DATE_CLOSED_DATE_FORMAT = "yyyy-MM-dd";

    /**
     * Default Server
     */
    private static String DHIS_SERVER = "https://www.psi-mis.org";

    /**
     * Endpoint to retrieve server info (including version)
     */
    private static final String DHIS_SERVER_INFO = "/api/system/info";

    /**
     * Endpoint to retrieve program info
     */
    private static final String DHIS_PULL_PROGRAM = "/api/programs/";

    /**
     * Endpoint suffix to retrieve program info
     */
    private static final String DHIS_EXIST_PROGRAM = ".json?fields=id";

    /**
     * Endpoint to retrieve orgUnits info filtering by CODE (API)
     */
    private static final String DHIS_PULL_ORG_UNIT_API = "/api/organisationUnits.json?paging=false&fields=id,closedDate&filter=code:eq:%s&filter:programs:id:eq:%s";

    /**
     * Endpoint to retrieve orgUnits info filtering by NAME (SDK)
     */
    private static final String DHIS_PULL_ORG_UNIT_API_BY_NAME = "/api/organisationUnits.json?paging=false&fields=id,closedDate&filter=name:eq:%s&filter:programs:id:eq:%s";

    /**
     * Endpoint suffix to retrieve orgUnits
     */
    private static final String DHIS_PULL_ORG_UNITS_API = ".json?fields=organisationUnits";
    /**
     * Endpoint to patch closeDate to an OrgUnit
     */
    private static final String DHIS_PATCH_URL_CLOSED_DATE = "/api/organisationUnits/%s/closedDate";

    /**
     * Endpoint to patch description to an OrgUnit
     */
    private static final String DHIS_PATCH_URL_DESCRIPTIONCLOSED_DATE = "/api/organisationUnits/%s/description";

    /**
     * New Description to a closed OrgUnit
     */
    private static final String DHIS_PATCH_DESCRIPTIONCLOSED_DATE = "[%s] - Android Surveillance App set the closing date to %s because over 30 surveys were pushed within 1 hour.";

    /**
     * MediaType always json + utf8
     */
    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

    /**
     * Max surveys that can be sent ...
     */
    private static int DHIS_LIMIT_SENT_SURVEYS_IN_ONE_HOUR = 30;

    /**
     * ... In an hour
     */
    private static int DHIS_LIMIT_HOURS = 1;

    /**
     * Current program UID (once is calculated never changes)
     */
    private static String programUID;

    /**
     * Returns current serverUrl
     * @return
     */
    public static String getServerUrl() {
        return PreferencesState.getInstance().getDhisURL();
    }

    /**
     * Returns current orgUnit
     * @return
     */
    public static String getOrgUnit() {
        return PreferencesState.getInstance().getOrgUnit();
    }

    /**
     * Returns the UID of the pictureapp program (from db)
     * @return
     */
    public static String getProgramUID() {
        if (programUID == null) {
            programUID = Program.getFirstProgram().getUid();
        }
        return programUID;
    }

    /**
     * Returns hardcoded credentials for its use in sdk
     * @return
     */
    public static org.hisp.dhis.android.sdk.network.Credentials getSDKCredentials() {
        return new org.hisp.dhis.android.sdk.network.Credentials(DHIS_USERNAME, DHIS_PASSWORD);
    }

    /**
     * Returns the version of the default server
     * Null if something went wrong
     * @return
     */
    public static String getServerVersion() {
        return getServerVersion(DHIS_SERVER);
    }

    /**
     * Returns the version of the given server.
     * Null if something went wrong
     * @param url
     * @return
     */
    public static String getServerVersion(String url) {
        String serverVersion;
        try {
            String urlServerInfo = url + DHIS_SERVER_INFO;
            Response response = executeCall(null, urlServerInfo, "GET");

            //Error -> null
            if (!response.isSuccessful()) {
                Log.e(TAG, "getServerVersion (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }
            JSONObject data = parseResponse(response.body().string());
            serverVersion = data.getString(TAG_VERSION);
        } catch (Exception ex) {
            Log.e(TAG, "getServerVersion: " + ex.toString());
            serverVersion = "";
        }
        Log.i(TAG, String.format("getServerVersion(%s) -> %s", url, serverVersion));
        return serverVersion;
    }

    /**
     * Checks if the given url corresponds to a 2.20 server (uses API for some ops)
     * @return
     */
    public static boolean isAPIServer() {
        return isAPIServer(getServerUrl());
    }

    /**
     * Checks if the given url corresponds to a 2.20 server (uses API for some ops)
     * @param url
     * @return
     */
    public static boolean isAPIServer(String url) {
        String serverVersion = getServerVersion(url);
        return isAPIVersion(serverVersion);
    }

    /**
     * Checks if the given version corresponds to 2.20
     * @param serverVersion
     * @return
     */
    public static boolean isAPIVersion(String serverVersion) {
        return Constants.DHIS_API_SERVER.equals(serverVersion);
    }

    /**
     * Returns true|false depending of the network connectivity.
     * @return
     */
    public static boolean isNetworkAvailable() {
        Context ctx = PreferencesState.getInstance().getContext();
        ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (activeNetwork == null) {
            return false;
        }
        return activeNetwork.isConnectedOrConnecting();
    }

    /**
     * Checks if data can be pushed into the server
     * @return
     */
    public static boolean isReadyForPush() {
        String serverUrl = getServerUrl();
        String orgUnit = getOrgUnit();
        return isReadyForPush(serverUrl, orgUnit);
    }

    /**
     * Checks if data can be pushed into the server
     * @param url
     * @param orgUnitCodeOrName
     * @return
     */
    public static boolean isReadyForPush(String url, String orgUnitCodeOrName) {
        if (!isNetworkAvailable()) {
            Log.w(TAG, String.format("isReadyForPush(%s,%s) -> Network not available", url, orgUnitCodeOrName));
            return false;
        }

        if (!isValidProgram(url)) {
            Log.w(TAG,
                    String.format("isReadyForPush(%s,%s) -> Program not found in server", url, orgUnitCodeOrName));
            return false;
        }

        if (orgUnitCodeOrName == null || orgUnitCodeOrName.equals("") || !isValidOrgUnit(url, orgUnitCodeOrName)) {
            Log.w(TAG,
                    String.format("isReadyForPush(%s,%s) -> OrgUnit not found in server", url, orgUnitCodeOrName));
            return false;
        }

        if (!isOrgUnitOpen(url, orgUnitCodeOrName)) {
            Log.w(TAG, String.format("isOrgUnitOpen(%s,%s) -> OrgUnit closed, push is not enabled", url,
                    orgUnitCodeOrName));
            return false;
        }

        return true;
    }

    /**
     * Returns if the given url contains the current program
     * @param url
     * @return
     */
    public static boolean isValidProgram(String url) {
        Log.d(TAG, String.format("isValidProgram(%s) ...", url));
        String programUIDInServer;
        try {
            String urlValidProgram = getIsValidProgramUrl(url);
            Response response = executeCall(null, urlValidProgram, "GET");

            //Error -> null
            if (!response.isSuccessful()) {
                Log.e(TAG, "isValidProgram (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }

            JSONObject data = parseResponse(response.body().string());
            programUIDInServer = String.valueOf(data.get(TAG_ID));
        } catch (Exception ex) {
            Log.e(TAG, "isValidProgram: " + ex.toString());
            return false;
        }
        boolean valid = getProgramUID() != null && getProgramUID().equals(programUIDInServer);
        Log.d(TAG, String.format("isValidProgram(%s) -> %b", url, valid));
        return valid;
    }

    /**
     * Checks if the given orgUnit is present in the server.
     * XXX The endpoint changes from using code|name field in API|SDK servers.
     * @param url
     * @param orgUnitNameOrCode OrgUnit code if server is 2.20, OrgUnit name if server is 2.21,2.22
     * @return true|false
     */
    public static boolean isValidOrgUnit(String url, String orgUnitNameOrCode) {
        JSONObject orgUnitJSON = getOrgUnitData(url, orgUnitNameOrCode);
        return orgUnitJSON != null;
    }

    /**
     * Checks if the given orgUnit is open in the server.
     * @param url
     * @param orgUnitNameOrCode OrgUnit code if server is 2.20, OrgUnit name if server is 2.21,2.22
     * @return true|false
     */
    public static boolean isOrgUnitOpen(String url, String orgUnitNameOrCode) {
        JSONObject orgUnitJSON = getOrgUnitData(url, orgUnitNameOrCode);
        if (orgUnitJSON == null) {
            return false;
        }

        return !isBanned(orgUnitJSON);
    }

    /**
     * Closes server if too many surveys have been pushed
     */
    public static void banOrgUnitIfRequired() {
        banOrgUnitIfRequired(getServerUrl(), getOrgUnit());
    }

    /**
     * Closes server if too many surveys have been pushed
     */
    public static void banOrgUnitIfRequired(String url, String orgUnitNameOrCode) {
        List<Survey> sentSurveys = Survey.getAllHideAndSentSurveys();
        if (isSurveyOverLimit(sentSurveys)) {
            banOrg(url, orgUnitNameOrCode);
        }
    }

    /**
     * Returns the orgUnit UID for the current server + orgunit
     * @return
     */
    public static String getOrgUnitUID() {
        String serverUrl = getServerUrl();
        String orgUnit = getOrgUnit();
        return getOrgUnitUID(serverUrl, orgUnit);
    }

    /**
     * Returns the orgUnit UID for the given url and orgUnit (code or name)
     * @param url
     * @param orgUnitNameOrCode
     * @return
     */
    public static String getOrgUnitUID(String url, String orgUnitNameOrCode) {
        JSONObject orgUnitJSON = getOrgUnitData(url, orgUnitNameOrCode);
        if (orgUnitJSON == null) {
            return null;
        }
        try {
            return orgUnitJSON.getString(TAG_ID);
        } catch (JSONException ex) {
            return null;
        }
    }

    /**
     * Bans the orgUnit for future pushes (too many too quick)
     * @param url
     * @param orgUnitNameOrCode
     */
    public static void banOrg(String url, String orgUnitNameOrCode) {
        Log.i(TAG, String.format("banOrg(%s,%s)", url, orgUnitNameOrCode));
        try {
            JSONObject orgUnitJSON = getOrgUnitData(url, orgUnitNameOrCode);
            String orgUnitUID = orgUnitJSON.getString(TAG_ID);
            String orgUnitDescription = orgUnitJSON.getString(TAG_DESCRIPTIONCLOSEDATE);

            //NO OrgUnitUID -> Non blocking error, go on
            if (orgUnitUID == null) {
                Log.e(TAG, String.format("banOrg(%s,%s) -> No UID", url, orgUnitNameOrCode));
                return;
            }

            //Update date and descripcion in the orgunit
            patchClosedDate(url, orgUnitUID);
            patchDescriptionClosedDate(url, orgUnitUID, orgUnitDescription);
        } catch (Exception ex) {
            Log.e(TAG, String.format("banOrg(%s,%s): %s", url, orgUnitNameOrCode, ex.getMessage()));
        }
    }

    /**
     * This method returns a String[] whit the Organitation codes
     * @throws Exception
     */
    public static String[] pullOrgUnitsCodes(String url) {

        try {
            String orgUnitsURL = getDhisOrgUnitsURL(url);
            Response response = executeCall(null, orgUnitsURL, "GET");

            //Error -> null
            if (!response.isSuccessful()) {
                Log.e(TAG, "pullOrgUnitsCodes (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }

            //{"organisationUnits":[{}]}
            JSONObject jsonResponse = parseResponse(response.body().string());
            JSONArray orgUnitsArray = (JSONArray) jsonResponse.get(TAG_ORGANISATIONUNITS);

            //0 matches -> Error
            if (orgUnitsArray.length() == 0) {
                throw new Exception("Found 0 matches");
            }
            return Utils.jsonArrayToStringArray(orgUnitsArray, TAG_CODE);

        } catch (Exception ex) {
            Log.e(TAG, String.format("pullOrgUnitsCodes(%url): %s", url, ex.getMessage()));
            String[] value = new String[1];
            value[0] = "";
            return value;
        }

    }

    /**
     * compares the dates of the surveys and checks if the dates are over the limit
     * @param surveyList all the sent surveys
     * @return true if the surveys are over the limit
     */
    static boolean isSurveyOverLimit(List<Survey> surveyList) {
        if (surveyList.size() >= DHIS_LIMIT_SENT_SURVEYS_IN_ONE_HOUR) {
            for (int i = 0; i < surveyList.size(); i++) {
                int countDates = 0;
                Calendar actualSurvey = Utils.DateToCalendar(surveyList.get(i).getEventDate());
                for (int d = 0; d < surveyList.size(); d++) {
                    Calendar nextSurvey = Utils.DateToCalendar(surveyList.get(d).getEventDate());
                    if (actualSurvey.before(nextSurvey)) {
                        if (!Utils.isDateOverLimit(actualSurvey, nextSurvey, DHIS_LIMIT_HOURS)) {
                            countDates++;
                            Log.d(TAG, "Surveys sents in one hour:" + countDates);
                            if (countDates >= DHIS_LIMIT_SENT_SURVEYS_IN_ONE_HOUR) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * Updates the orgUnit adding a closedDate
     * @param url
     * @param orgUnitUID
     */
    static void patchClosedDate(String url, String orgUnitUID) {
        //https://malariacare.psi.org/api/organisationUnits/u5jlxuod8xQ/closedDate
        try {
            String urlPathClosedDate = getPatchClosedDateUrl(url, orgUnitUID);
            JSONObject data = prepareTodayDateValue();
            Response response = executeCall(data, urlPathClosedDate, "PATCH");
            if (!response.isSuccessful()) {
                Log.e(TAG, "closingDatePatch (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }
        } catch (Exception e) {
            Log.e(TAG, String.format("patchClosedDate(%s,%s): %s", url, orgUnitUID, e.getMessage()));
        }
    }

    /**
     * Prepare the closing value.
     * @return Closing value as Json.
     */
    static JSONObject prepareTodayDateValue() throws Exception {
        String dateFormatted = Utils.geTodayDataString(DATE_CLOSED_DATE_FORMAT);
        JSONObject elementObject = new JSONObject();
        elementObject.put(TAG_CLOSEDDATE, dateFormatted);
        return elementObject;
    }

    static void patchDescriptionClosedDate(String url, String orgUnitUID, String orgUnitDescription) {
        //https://malariacare.psi.org/api/organisationUnits/u5jlxuod8xQ/closedDate
        try {
            String urlPathClosedDescription = getPatchClosedDescriptionUrl(url, orgUnitUID);
            JSONObject data = prepareClosingDescriptionValue(orgUnitDescription);
            Response response = executeCall(data, urlPathClosedDescription, "PATCH");
            if (!response.isSuccessful()) {
                Log.e(TAG, "patchDescriptionClosedDate (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }
        } catch (Exception e) {
            Log.e(TAG, String.format("patchDescriptionClosedDate(%s,%s): %s", url, orgUnitUID, e.getMessage()));
        }
    }

    /**
     * Pull the current description and adds new closed organization description.
     * @url url for pull the current description
     * @return new description.
     */
    static JSONObject prepareClosingDescriptionValue(String orgUnitDescription) throws Exception {

        //New line to description
        String dateFormatted = Utils.getClosingDateString("dd-MM-yyyy");
        String dateTimestamp = Utils.getClosingDateTimestamp(Utils.getClosingDateString("dd-MM-yyyy")).getTime()
                + "";
        String description = String.format(DHIS_PATCH_DESCRIPTIONCLOSED_DATE, dateTimestamp, dateFormatted);

        //Previous + New line
        StringBuilder sb = new StringBuilder();
        sb.append(orgUnitDescription);
        sb.append("");//next line
        sb.append("");//next line
        sb.append(description);
        description = sb.toString();
        sb = null;

        //As a JSON
        JSONObject elementObject = new JSONObject();
        elementObject.put(TAG_DESCRIPTIONCLOSEDATE, description);
        return elementObject;
    }

    /**
     * Returns the orgunit data from the given server according to its current version
     * @param url
     * @param orgUnitNameOrCode
     * @return
     */
    static JSONObject getOrgUnitData(String url, String orgUnitNameOrCode) {
        //Version is required to choose which field to match
        String serverVersion = getServerVersion(url);

        //No version -> No data
        if (serverVersion == null) {
            return null;
        }

        try {
            String urlOrgUnitData = getOrgUnitDataUrl(url, serverVersion, orgUnitNameOrCode);
            Response response = executeCall(null, urlOrgUnitData, "GET");

            //Error -> null
            if (!response.isSuccessful()) {
                Log.e(TAG, "getOrgUnitData (" + response.code() + "): " + response.body().string());
                throw new IOException(response.message());
            }

            //{"organisationUnits":[{}]}
            JSONObject jsonResponse = parseResponse(response.body().string());
            JSONArray orgUnitsArray = (JSONArray) jsonResponse.get(TAG_ORGANISATIONUNITS);

            //0| >1 matches -> Error
            if (orgUnitsArray.length() == 0 || orgUnitsArray.length() > 1) {
                Log.e(TAG, String.format("getOrgUnitData(%s,%s) -> Found %d matches", url, orgUnitNameOrCode,
                        orgUnitsArray.length()));
                return null;
            }
            return (JSONObject) orgUnitsArray.get(0);

        } catch (Exception ex) {
            Log.e(TAG, String.format("getOrgUnitData(%s,%s): %s", url, orgUnitNameOrCode, ex.toString()));
            return null;
        }

    }

    /**
     * Checks if the orgunit is closed (due to too much surveys being pushed)
     * @param orgUnitJSON
     * @return
     */
    static boolean isBanned(JSONObject orgUnitJSON) {
        if (orgUnitJSON == null) {
            return true;
        }
        Log.d(TAG, String.format("isBanned(%s)", orgUnitJSON.toString()));
        try {
            String closedDateAsString = getClosedDate(orgUnitJSON);
            //No closedDate -> Open
            if (closedDateAsString == null || closedDateAsString.isEmpty()) {
                return false;
            }

            //CloseDate -> Check dates
            Calendar calendarClosedDate = Utils.parseStringToCalendar(closedDateAsString);

            //ClosedDate bad format -> Closed
            if (calendarClosedDate == null) {
                return true;
            }

            //If closeddate>today -> Closed
            return !Utils.isDateOverSystemDate(calendarClosedDate);

        } catch (Exception ex) {
            Log.e(TAG, String.format("isBanned(%s) ->%s", orgUnitJSON.toString(), ex.getMessage()));
            return true;
        }
    }

    /**
     * Returns the closedDate from the given orgUnit (json format) or null if it is not present
     * (which is fine too)
     * @param orgUnitJSON
     * @return
     */
    static String getClosedDate(JSONObject orgUnitJSON) {
        try {
            return orgUnitJSON.getString(TAG_CLOSEDDATE);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     *
     * This method returns the valid url for check the program
     * @return url for ask if the program uid exist with the UID_PROGRAM value.
     */
    static String getIsValidProgramUrl(String url) {
        String endpoint = url + DHIS_PULL_PROGRAM + getProgramUID() + DHIS_EXIST_PROGRAM;
        endpoint = encodeBlanks(endpoint);
        Log.d(TAG, String.format("getIsValidProgramUrl(%s)->%s", url, endpoint));
        return endpoint;
    }

    /**
     * Returns the right endpoint depending on the server version
     * @param url
     * @param serverVersion
     * @param orgUnitNameOrCode
     * @return
     */
    static String getOrgUnitDataUrl(String url, String serverVersion, String orgUnitNameOrCode) {
        String endpoint = url;
        String programUID = getProgramUID();
        if (Constants.DHIS_API_SERVER.equals(serverVersion)) {
            endpoint += String.format(DHIS_PULL_ORG_UNIT_API, orgUnitNameOrCode, programUID);
        } else {
            endpoint += String.format(DHIS_PULL_ORG_UNIT_API_BY_NAME, orgUnitNameOrCode, programUID);
        }

        endpoint = encodeBlanks(endpoint);
        Log.d(TAG, String.format("getOrgUnitDataUrl(%s,%s,%s) -> %s", url, serverVersion, orgUnitNameOrCode,
                endpoint));
        return endpoint;
    }

    /**
     * Returns the ClosedDate that points to the DHIS server (Pull) API according to preferences.
     * @return
     */
    static String getPatchClosedDateUrl(String url, String orguid) {
        //Get the org_ID
        String endpoint = url + String.format(DHIS_PATCH_URL_CLOSED_DATE, orguid);
        return encodeBlanks(endpoint);
    }

    /**
     * Returns the Description of orgUnit that points to the DHIS server (Pull) API according to preferences.
     * @return
     */
    static String getPatchClosedDescriptionUrl(String url, String orguid) {
        String endpoint = url + String.format(DHIS_PATCH_URL_DESCRIPTIONCLOSED_DATE, orguid);
        return encodeBlanks(endpoint);
    }

    /**
     * Returns the URL that points to the DHIS server (Pull) API according to preferences.
     * @return
     */
    static String getDhisOrgUnitsURL(String url) {
        String endpoint = url + DHIS_PULL_PROGRAM + getProgramUID() + DHIS_PULL_ORG_UNITS_API;
        return encodeBlanks(endpoint);
    }

    static String encodeBlanks(String endpoint) {
        return endpoint.replace(" ", "%20");
    }

    /**
     * Call to DHIS Server
     * @param data
     * @param url
     */
    static Response executeCall(JSONObject data, String url, String method) throws IOException {
        final String DHIS_URL = url;

        OkHttpClient client = UnsafeOkHttpsClientFactory.getUnsafeOkHttpClient();

        BasicAuthenticator basicAuthenticator = new BasicAuthenticator();
        client.setAuthenticator(basicAuthenticator);

        Request.Builder builder = new Request.Builder()
                .header(basicAuthenticator.AUTHORIZATION_HEADER, basicAuthenticator.getCredentials()).url(DHIS_URL);

        switch (method) {
        case "POST":
            RequestBody postBody = RequestBody.create(JSON, data.toString());
            builder.post(postBody);
            break;
        case "PUT":
            RequestBody putBody = RequestBody.create(JSON, data.toString());
            builder.put(putBody);
            break;
        case "PATCH":
            RequestBody patchBody = RequestBody.create(JSON, data.toString());
            builder.patch(patchBody);
            break;
        case "GET":
            builder.get();
            break;
        }

        Request request = builder.build();
        return client.newCall(request).execute();
    }

    /**
     * Turns a string response into a handy JSONObject.
     * Returns null if its possible
     * @param responseData
     * @return
     * @throws Exception
     */
    static JSONObject parseResponse(String responseData) {
        try {
            JSONObject jsonResponse = new JSONObject(responseData);
            Log.d(TAG, "parseResponse: " + jsonResponse);
            return jsonResponse;
        } catch (Exception ex) {
            return null;
        }
    }

}

/**
 * Basic authenticator required for calls
 */
class BasicAuthenticator implements Authenticator {

    public final String AUTHORIZATION_HEADER = "Authorization";
    private String credentials;

    BasicAuthenticator() {
        credentials = Credentials.basic(ServerAPIController.DHIS_USERNAME, ServerAPIController.DHIS_PASSWORD);
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
        return response.request().newBuilder().header(AUTHORIZATION_HEADER, credentials).build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        return null;
    }

    public String getCredentials() {
        return credentials;
    }
}