de.symeda.sormas.app.rest.RetroProvider.java Source code

Java tutorial

Introduction

Here is the source code for de.symeda.sormas.app.rest.RetroProvider.java

Source

/*
 * SORMAS - Surveillance Outbreak Response Management & Analysis System
 * Copyright  2016-2018 Helmholtz-Zentrum fr Infektionsforschung GmbH (HZI)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.symeda.sormas.app.rest;

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

import androidx.fragment.app.FragmentActivity;

import com.google.android.gms.analytics.Tracker;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import de.symeda.sormas.api.caze.classification.ClassificationAllOfCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationCaseCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationEpiDataCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationNoneOfCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationNotInStartDateRangeCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationPathogenTestCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationPathogenTestPositiveResultCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationPersonAgeBetweenYearsCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationSymptomsCriteriaDto;
import de.symeda.sormas.api.caze.classification.ClassificationXOfCriteriaDto;
import de.symeda.sormas.api.utils.CompatibilityCheckResponse;
import de.symeda.sormas.api.utils.DataHelper;
import de.symeda.sormas.api.utils.InfoProvider;
import de.symeda.sormas.app.R;
import de.symeda.sormas.app.SormasApplication;
import de.symeda.sormas.app.backend.config.ConfigProvider;
import de.symeda.sormas.app.backend.user.User;
import de.symeda.sormas.app.core.NotificationContext;
import de.symeda.sormas.app.core.async.AsyncTaskResult;
import de.symeda.sormas.app.core.async.DefaultAsyncTask;
import de.symeda.sormas.app.core.async.TaskResultHolder;
import de.symeda.sormas.app.core.notification.NotificationHelper;
import de.symeda.sormas.app.core.notification.NotificationType;
import de.symeda.sormas.app.util.AppUpdateController;
import de.symeda.sormas.app.util.BiConsumer;
import de.symeda.sormas.app.util.Callback;
import de.symeda.sormas.app.util.Consumer;
import de.symeda.sormas.app.util.ErrorReportingHelper;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public final class RetroProvider {

    private static int lastConnectionId = 0;
    private static RetroProvider instance = null;

    private final Context context;
    private final Retrofit retrofit;

    private InfoFacadeRetro infoFacadeRetro;
    private CaseFacadeRetro caseFacadeRetro;
    private PersonFacadeRetro personFacadeRetro;
    private CommunityFacadeRetro communityFacadeRetro;
    private DistrictFacadeRetro districtFacadeRetro;
    private RegionFacadeRetro regionFacadeRetro;
    private FacilityFacadeRetro facilityFacadeRetro;
    private PointOfEntryFacadeRetro pointOfEntryFacadeRetro;
    private UserFacadeRetro userFacadeRetro;
    private TaskFacadeRetro taskFacadeRetro;
    private ContactFacadeRetro contactFacadeRetro;
    private VisitFacadeRetro visitFacadeRetro;
    private EventFacadeRetro eventFacadeRetro;
    private SampleFacadeRetro sampleFacadeRetro;
    private PathogenTestFacadeRetro pathogenTestFacadeRetro;
    private EventParticipantFacadeRetro eventParticipantFacadeRetro;
    private WeeklyReportFacadeRetro weeklyReportFacadeRetro;
    private OutbreakFacadeRetro outbreakFacadeRetro;
    private ClassificationFacadeRetro classificationFacadeRetro;
    private UserRoleConfigFacadeRetro userRoleConfigFacadeRetro;
    private PrescriptionFacadeRetro prescriptionFacadeRetro;
    private TreatmentFacadeRetro treatmentFacadeRetro;
    private AdditionalTestFacadeRetro additionalTestFacadeRetro;
    private ClinicalVisitFacadeRetro clinicalVisitFacadeRetro;
    private DiseaseConfigurationFacadeRetro diseaseConfigurationFacadeRetro;

    private RetroProvider(Context context)
            throws ServerConnectionException, ServerCommunicationException, ApiVersionException {

        lastConnectionId = this.hashCode();

        this.context = context;

        RuntimeTypeAdapterFactory<ClassificationCriteriaDto> classificationCriteriaFactory = RuntimeTypeAdapterFactory
                .of(ClassificationCriteriaDto.class, "type")
                .registerSubtype(ClassificationAllOfCriteriaDto.class, "ClassificationAllOfCriteriaDto")
                .registerSubtype(ClassificationCaseCriteriaDto.class, "ClassificationCaseCriteriaDto")
                .registerSubtype(ClassificationNoneOfCriteriaDto.class, "ClassificationNoneOfCriteriaDto")
                .registerSubtype(ClassificationPersonAgeBetweenYearsCriteriaDto.class,
                        "ClassificationPersonAgeBetweenYearsCriteriaDto")
                .registerSubtype(ClassificationPathogenTestPositiveResultCriteriaDto.class,
                        "ClassificationPathogenTestPositiveResultCriteriaDto")
                .registerSubtype(ClassificationXOfCriteriaDto.class, "ClassificationXOfCriteriaDto")
                .registerSubtype(ClassificationEpiDataCriteriaDto.class, "ClassificationEpiDataCriteriaDto")
                .registerSubtype(ClassificationNotInStartDateRangeCriteriaDto.class,
                        "ClassificationNotInStartDateRangeCriteriaDto")
                .registerSubtype(ClassificationSymptomsCriteriaDto.class, "ClassificationSymptomsCriteriaDto")
                .registerSubtype(ClassificationPathogenTestCriteriaDto.class,
                        "ClassificationPathogenTestCriteriaDto")
                .registerSubtype(ClassificationXOfCriteriaDto.ClassificationXOfSubCriteriaDto.class,
                        "ClassificationXOfSubCriteriaDto")
                .registerSubtype(ClassificationXOfCriteriaDto.ClassificationOneOfCompactCriteriaDto.class,
                        "ClassificationOneOfCompactCriteriaDto")
                .registerSubtype(ClassificationAllOfCriteriaDto.ClassificationAllOfCompactCriteriaDto.class,
                        "ClassificationAllOfCompactCriteriaDto");

        Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
            public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                    throws JsonParseException {
                if (json.isJsonNull()) {
                    return null;
                }
                long milliseconds = json.getAsLong();
                return new Date(milliseconds);
            }
        }).registerTypeAdapter(Date.class, new JsonSerializer<Date>() {
            @Override
            public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
                if (src == null) {
                    return JsonNull.INSTANCE;
                }
                return new JsonPrimitive(src.getTime());
            }
        }).registerTypeAdapterFactory(classificationCriteriaFactory).create();

        // Basic auth as explained in https://futurestud.io/tutorials/android-basic-authentication-with-retrofit

        String authToken = Credentials.basic(ConfigProvider.getUsername(), ConfigProvider.getPassword());
        AuthenticationInterceptor interceptor = new AuthenticationInterceptor(authToken);

        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        httpClient.connectTimeout(20, TimeUnit.SECONDS);
        httpClient.readTimeout(60, TimeUnit.SECONDS); // for infrastructure data
        httpClient.writeTimeout(30, TimeUnit.SECONDS);

        // adds "Accept-Encoding: gzip" by default
        httpClient.addInterceptor(interceptor);

        // header for logging purposes
        httpClient.addInterceptor(chain -> {

            Request original = chain.request();
            Request.Builder builder = original.newBuilder();

            User user = ConfigProvider.getUser();
            if (user != null) {
                builder.header("User", DataHelper.getShortUuid(user.getUuid()));
                builder.header("Connection", String.valueOf(lastConnectionId)); // not sure if this is a good solution
            }

            builder.method(original.method(), original.body());
            return chain.proceed(builder.build());
        });

        retrofit = new Retrofit.Builder().baseUrl(ConfigProvider.getServerRestUrl())
                .addConverterFactory(GsonConverterFactory.create(gson)).client(httpClient.build()).build();

        checkCompatibility();

        updateLocale();
    }

    public static int getLastConnectionId() {
        return lastConnectionId;
    }

    private void updateLocale() throws ServerCommunicationException, ServerConnectionException {
        Response<String> localeResponse;
        infoFacadeRetro = retrofit.create(InfoFacadeRetro.class);
        Call<String> localeCall = infoFacadeRetro.getLocale();
        try {
            localeResponse = localeCall.execute();
        } catch (IOException e) {
            Log.w(RetroProvider.class.getSimpleName(), e.getMessage());
            // wrap the exception message inside a response object
            localeResponse = Response.error(500,
                    ResponseBody.create(MediaType.parse("text/plain"), e.getMessage()));
        }

        if (localeResponse.isSuccessful()) {
            // success - now check compatibility
            String localeStr = localeResponse.body();
            ConfigProvider.setLocale(localeStr);
        } else {
            throwException(localeResponse);
        }
    }

    private void checkCompatibility()
            throws ServerCommunicationException, ServerConnectionException, ApiVersionException {

        Response<CompatibilityCheckResponse> compatibilityResponse;
        // make call to get version info
        infoFacadeRetro = retrofit.create(InfoFacadeRetro.class);
        Call<CompatibilityCheckResponse> compatibilityCall = infoFacadeRetro
                .isCompatibleToApi(InfoProvider.get().getVersion());
        try {
            compatibilityResponse = compatibilityCall.execute();
        } catch (IOException e) {
            Log.w(RetroProvider.class.getSimpleName(), e.getMessage());
            // wrap the exception message inside a response object
            compatibilityResponse = Response.error(500,
                    ResponseBody.create(MediaType.parse("text/plain"), e.getMessage()));
        }

        if (compatibilityResponse.isSuccessful()) {
            // success - now check compatibility
            CompatibilityCheckResponse compatibilityCheckResponse = compatibilityResponse.body();
            if (compatibilityCheckResponse == CompatibilityCheckResponse.TOO_NEW) {
                throw new ServerConnectionException(601);
            } else if (compatibilityCheckResponse == CompatibilityCheckResponse.TOO_OLD) {
                // get the current server version, throw an exception including the app url that is then processed in the UI
                matchAppAndApiVersions(infoFacadeRetro);
            }
        } else {
            throwException(compatibilityResponse);
        }
    }

    public static boolean isConnectedToNetwork(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        return activeNetwork != null && activeNetwork.isConnected();
    }

    public static boolean isConnected() {
        return instance != null && isConnectedToNetwork(instance.context);
    }

    public static void connect(Context context)
            throws ApiVersionException, ServerConnectionException, ServerCommunicationException {

        if (RetroProvider.isConnected()) {
            throw new IllegalStateException("Connection already established.");
        }

        if (!isConnectedToNetwork(context)) {
            throw new ServerConnectionException(600);
        }

        try {
            instance = new RetroProvider(context);
        } catch (Exception e) {
            instance = null;
            throw e;
        }
    }

    public static void connectAsyncHandled(FragmentActivity activity, final boolean showUpgradePrompt,
            final boolean matchExactVersion, final Consumer<Boolean> callback) {

        if (!(activity instanceof NotificationContext)) {
            throw new UnsupportedOperationException(
                    "Activity needs to implement NotificationContext: " + activity.toString());
        }

        WeakReference<FragmentActivity> activityReference = new WeakReference<>(activity);

        connectAsync(activity.getApplicationContext(), matchExactVersion, (result, versionCompatible) -> {
            if (result.getResultStatus().isSuccess()) {
                callback.accept(true);
            } else {
                if (result.getError() instanceof ApiVersionException) {
                    ApiVersionException e = (ApiVersionException) result.getError();
                    if (showUpgradePrompt && !DataHelper.isNullOrEmpty(e.getAppUrl())
                            && activityReference.get() != null) {
                        boolean canWorkOffline = ConfigProvider.getUser() != null;
                        AppUpdateController.getInstance().updateApp(activityReference.get(), e.getAppUrl(),
                                e.getVersion(), versionCompatible || canWorkOffline, new Callback() {
                                    @Override
                                    public void call() {
                                        callback.accept(false);
                                    }
                                });
                    } else {
                        if (activityReference.get() != null) {
                            NotificationHelper.showNotification((NotificationContext) activityReference.get(),
                                    NotificationType.ERROR, e.getMessage());
                        }
                        callback.accept(false);
                    }
                } else if (result.getError() instanceof ServerConnectionException) {
                    ServerConnectionException exception = (ServerConnectionException) result.getError();

                    if (exception.getCustomHtmlErrorCode() == 401 || exception.getCustomHtmlErrorCode() == 403) {
                        // could not authenticate or user does not have access to the app
                        ConfigProvider.clearUsernameAndPassword();
                    }

                    if (activityReference.get() != null) {
                        NotificationHelper.showNotification((NotificationContext) activityReference.get(),
                                NotificationType.ERROR,
                                exception.getMessage(activityReference.get().getApplicationContext()));
                    }
                    callback.accept(false);
                } else {
                    if (activityReference.get() != null) {
                        NotificationHelper.showNotification((NotificationContext) activityReference.get(),
                                NotificationType.ERROR,
                                activityReference.get().getResources().getString(R.string.error_server_connection));
                    }
                    callback.accept(false);
                }
            }
        });
    }

    public static void connectAsync(Context context, final boolean matchExactVersion,
            final BiConsumer<AsyncTaskResult<TaskResultHolder>, Boolean> callback) {

        new DefaultAsyncTask(context) {

            boolean versionCompatible = false;

            @Override
            protected void doInBackground(TaskResultHolder resultHolder) throws NoConnectionException,
                    ServerConnectionException, ServerCommunicationException, ApiVersionException {
                RetroProvider.connect(getApplicationReference().get());
                versionCompatible = true;
                if (matchExactVersion) {
                    RetroProvider.matchAppAndApiVersions(getInfoFacade());
                }
            }

            @Override
            protected AsyncTaskResult handleException(Exception e) {
                if (e instanceof ServerConnectionException || e instanceof ApiVersionException)
                    return new AsyncTaskResult<>(e); // expected exceptions
                return super.handleException(e);
            }

            @Override
            protected void onPostExecute(AsyncTaskResult<TaskResultHolder> taskResult) {
                callback.accept(taskResult, versionCompatible);
            }

        }.executeOnThreadPool();
    }

    public static void disconnect() {
        instance = null;
    }

    private static void matchAppAndApiVersions(final InfoFacadeRetro infoFacadeRetro)
            throws ServerCommunicationException, ServerConnectionException, ApiVersionException {
        // Retrieve the version
        Response<String> versionResponse;
        Call<String> versionCall = infoFacadeRetro.getVersion();
        try {
            versionResponse = versionCall.execute();
        } catch (IOException e) {
            // wrap the exception message inside a response object
            versionResponse = Response.error(500,
                    ResponseBody.create(MediaType.parse("text/plain"), e.getMessage()));
        }

        if (versionResponse.isSuccessful()) {
            // Check if the versions match
            String serverApiVersion = versionResponse.body();
            String appApiVersion = InfoProvider.get().getVersion();
            if (!serverApiVersion.equals(appApiVersion)) {
                // Retrieve the app URL
                Response<String> appUrlResponse;
                Call<String> appUrlCall = infoFacadeRetro.getAppUrl(InfoProvider.get().getVersion());
                try {
                    appUrlResponse = appUrlCall.execute();
                } catch (IOException e) {
                    // wrap the exception message inside a response object
                    appUrlResponse = Response.error(500,
                            ResponseBody.create(MediaType.parse("text/plain"), e.getMessage()));
                }

                if (appUrlResponse.isSuccessful()) {
                    throw new ApiVersionException("App version '" + appApiVersion
                            + "' does not match server version '" + serverApiVersion + "'", appUrlResponse.body(),
                            serverApiVersion);
                } else {
                    throwException(appUrlResponse);
                }
            }
        } else {
            throwException(versionResponse);
        }
    }

    public static InfoFacadeRetro getInfoFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        return instance.infoFacadeRetro;
    }

    public static CaseFacadeRetro getCaseFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.caseFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.caseFacadeRetro == null) {
                    instance.caseFacadeRetro = instance.retrofit.create(CaseFacadeRetro.class);
                }
            }
        }
        return instance.caseFacadeRetro;
    }

    public static PersonFacadeRetro getPersonFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.personFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.personFacadeRetro == null) {
                    instance.personFacadeRetro = instance.retrofit.create(PersonFacadeRetro.class);
                }
            }
        }
        return instance.personFacadeRetro;
    }

    public static CommunityFacadeRetro getCommunityFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.communityFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.communityFacadeRetro == null) {
                    instance.communityFacadeRetro = instance.retrofit.create(CommunityFacadeRetro.class);
                }
            }
        }
        return instance.communityFacadeRetro;
    }

    public static DistrictFacadeRetro getDistrictFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.districtFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.districtFacadeRetro == null) {
                    instance.districtFacadeRetro = instance.retrofit.create(DistrictFacadeRetro.class);
                }
            }
        }
        return instance.districtFacadeRetro;
    }

    public static RegionFacadeRetro getRegionFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.regionFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.regionFacadeRetro == null) {
                    instance.regionFacadeRetro = instance.retrofit.create(RegionFacadeRetro.class);
                }
            }
        }
        return instance.regionFacadeRetro;
    }

    public static FacilityFacadeRetro getFacilityFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.facilityFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.facilityFacadeRetro == null) {
                    instance.facilityFacadeRetro = instance.retrofit.create(FacilityFacadeRetro.class);
                }
            }
        }
        return instance.facilityFacadeRetro;
    }

    public static PointOfEntryFacadeRetro getPointOfEntryFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.pointOfEntryFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.pointOfEntryFacadeRetro == null) {
                    instance.pointOfEntryFacadeRetro = instance.retrofit.create(PointOfEntryFacadeRetro.class);
                }
            }
        }
        return instance.pointOfEntryFacadeRetro;
    }

    public static UserFacadeRetro getUserFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.userFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.userFacadeRetro == null) {
                    instance.userFacadeRetro = instance.retrofit.create(UserFacadeRetro.class);
                }
            }
        }
        return instance.userFacadeRetro;
    }

    public static TaskFacadeRetro getTaskFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.taskFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.taskFacadeRetro == null) {
                    instance.taskFacadeRetro = instance.retrofit.create(TaskFacadeRetro.class);
                }
            }
        }
        return instance.taskFacadeRetro;
    }

    public static ContactFacadeRetro getContactFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.contactFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.contactFacadeRetro == null) {
                    instance.contactFacadeRetro = instance.retrofit.create(ContactFacadeRetro.class);
                }
            }
        }
        return instance.contactFacadeRetro;
    }

    public static VisitFacadeRetro getVisitFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.visitFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.visitFacadeRetro == null) {
                    instance.visitFacadeRetro = instance.retrofit.create(VisitFacadeRetro.class);
                }
            }
        }
        return instance.visitFacadeRetro;
    }

    public static EventFacadeRetro getEventFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.eventFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.eventFacadeRetro == null) {
                    instance.eventFacadeRetro = instance.retrofit.create(EventFacadeRetro.class);
                }
            }
        }
        return instance.eventFacadeRetro;
    }

    public static SampleFacadeRetro getSampleFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.sampleFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.sampleFacadeRetro == null) {
                    instance.sampleFacadeRetro = instance.retrofit.create(SampleFacadeRetro.class);
                }
            }
        }
        return instance.sampleFacadeRetro;
    }

    public static PathogenTestFacadeRetro getSampleTestFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.pathogenTestFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.pathogenTestFacadeRetro == null) {
                    instance.pathogenTestFacadeRetro = instance.retrofit.create(PathogenTestFacadeRetro.class);
                }
            }
        }
        return instance.pathogenTestFacadeRetro;
    }

    public static EventParticipantFacadeRetro getEventParticipantFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.eventParticipantFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.eventParticipantFacadeRetro == null) {
                    instance.eventParticipantFacadeRetro = instance.retrofit
                            .create(EventParticipantFacadeRetro.class);
                }
            }
        }
        return instance.eventParticipantFacadeRetro;
    }

    public static WeeklyReportFacadeRetro getWeeklyReportFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.weeklyReportFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.weeklyReportFacadeRetro == null) {
                    instance.weeklyReportFacadeRetro = instance.retrofit.create(WeeklyReportFacadeRetro.class);
                }
            }
        }
        return instance.weeklyReportFacadeRetro;
    }

    public static OutbreakFacadeRetro getOutbreakFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.outbreakFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.outbreakFacadeRetro == null) {
                    instance.outbreakFacadeRetro = instance.retrofit.create(OutbreakFacadeRetro.class);
                }
            }
        }
        return instance.outbreakFacadeRetro;
    }

    public static ClassificationFacadeRetro getClassificationFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.classificationFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.classificationFacadeRetro == null) {
                    instance.classificationFacadeRetro = instance.retrofit.create(ClassificationFacadeRetro.class);
                }
            }
        }
        return instance.classificationFacadeRetro;
    }

    public static UserRoleConfigFacadeRetro getUserRoleConfigFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.userRoleConfigFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.userRoleConfigFacadeRetro == null) {
                    instance.userRoleConfigFacadeRetro = instance.retrofit.create(UserRoleConfigFacadeRetro.class);
                }
            }
        }
        return instance.userRoleConfigFacadeRetro;
    }

    public static PrescriptionFacadeRetro getPrescriptionFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.prescriptionFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.prescriptionFacadeRetro == null) {
                    instance.prescriptionFacadeRetro = instance.retrofit.create(PrescriptionFacadeRetro.class);
                }
            }
        }
        return instance.prescriptionFacadeRetro;
    }

    public static TreatmentFacadeRetro getTreatmentFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.treatmentFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.treatmentFacadeRetro == null) {
                    instance.treatmentFacadeRetro = instance.retrofit.create(TreatmentFacadeRetro.class);
                }
            }
        }
        return instance.treatmentFacadeRetro;
    }

    public static AdditionalTestFacadeRetro getAdditionalTestFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.additionalTestFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.additionalTestFacadeRetro == null) {
                    instance.additionalTestFacadeRetro = instance.retrofit.create(AdditionalTestFacadeRetro.class);
                }
            }
        }
        return instance.additionalTestFacadeRetro;
    }

    public static ClinicalVisitFacadeRetro getClinicalVisitFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.clinicalVisitFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.clinicalVisitFacadeRetro == null) {
                    instance.clinicalVisitFacadeRetro = instance.retrofit.create(ClinicalVisitFacadeRetro.class);
                }
            }
        }
        return instance.clinicalVisitFacadeRetro;
    }

    public static DiseaseConfigurationFacadeRetro getDiseaseConfigurationFacade() throws NoConnectionException {
        if (instance == null)
            throw new NoConnectionException();
        if (instance.diseaseConfigurationFacadeRetro == null) {
            synchronized ((RetroProvider.class)) {
                if (instance.diseaseConfigurationFacadeRetro == null) {
                    instance.diseaseConfigurationFacadeRetro = instance.retrofit
                            .create(DiseaseConfigurationFacadeRetro.class);
                }
            }
        }
        return instance.diseaseConfigurationFacadeRetro;
    }

    public static void throwException(Response<?> response)
            throws ServerConnectionException, ServerCommunicationException {

        if (ServerConnectionException.RelatedErrorCodes.contains(response.code())) {
            throw new ServerConnectionException(response.code());
        } else {
            String responseErrorBodyString;
            try {
                responseErrorBodyString = response.errorBody().string();
            } catch (IOException e) {
                throw new RuntimeException("Exception accessing error body", e);
            }

            throw new ServerCommunicationException(responseErrorBodyString);
        }
    }
}