org.projectbuendia.client.net.OpenMrsServer.java Source code

Java tutorial

Introduction

Here is the source code for org.projectbuendia.client.net.OpenMrsServer.java

Source

// Copyright 2015 The Project Buendia Authors
//
// 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 distrib-
// uted 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
// specific language governing permissions and limitations under the License.

package org.projectbuendia.client.net;

import android.support.annotation.Nullable;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.projectbuendia.client.App;
import org.projectbuendia.client.data.app.AppEncounter;
import org.projectbuendia.client.data.app.AppPatient;
import org.projectbuendia.client.data.app.AppPatientDelta;
import org.projectbuendia.client.model.Concepts;
import org.projectbuendia.client.net.model.Encounter;
import org.projectbuendia.client.net.model.Location;
import org.projectbuendia.client.net.model.NewUser;
import org.projectbuendia.client.net.model.Patient;
import org.projectbuendia.client.net.model.User;
import org.projectbuendia.client.utils.Logger;
import org.projectbuendia.client.utils.Utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/** Implementation of {@link Server} that sends RPC's to OpenMRS. */
public class OpenMrsServer implements Server {

    private static final Logger LOG = Logger.create();

    private final OpenMrsConnectionDetails mConnectionDetails;
    private final RequestFactory mRequestFactory;
    private final Gson mGson;

    /**
     * Constructs an interface to the OpenMRS server.
     * @param connectionDetails an {@link OpenMrsConnectionDetails} instance for communicating with
     *                          the server
     * @param requestFactory a {@link RequestFactory} for generating requests to OpenMRS
     * @param gson a {@link Gson} instance for serialization/deserialization
     */
    public OpenMrsServer(OpenMrsConnectionDetails connectionDetails, RequestFactory requestFactory, Gson gson) {
        mConnectionDetails = connectionDetails;
        mRequestFactory = requestFactory;
        // TODO: Inject a Gson instance here.
        mGson = gson;
    }

    /**
     * Wraps an ErrorListener so as to extract an error message from the JSON
     * content of a response, if possible.
     * @param errorListener An error listener.
     * @return A new error listener that tries to pass a more meaningful message
     *     to the original errorListener.
     */
    private Response.ErrorListener wrapErrorListener(final Response.ErrorListener errorListener) {
        return new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                String message = error.getMessage();
                try {
                    if (error.networkResponse != null && error.networkResponse.data != null) {
                        String text = new String(error.networkResponse.data);
                        JsonObject result = new JsonParser().parse(text).getAsJsonObject();
                        if (result.has("error")) {
                            JsonObject errorObject = result.getAsJsonObject("error");
                            JsonElement element = errorObject.get("message");
                            if (element == null || element.isJsonNull()) {
                                element = errorObject.get("code");
                            }
                            if (element != null && element.isJsonPrimitive()) {
                                message = element.getAsString();
                            }
                        }
                    }
                } catch (JsonParseException | IllegalStateException | UnsupportedOperationException e) {
                    e.printStackTrace();
                }
                errorListener.onErrorResponse(new VolleyError(message, error));
            }
        };
    }

    @Override
    public void logToServer(List<String> pairs) {
        // To avoid filling the server logs with big messy stack traces, let's make a dummy
        // request that succeeds.  We assume "Pulse" will always be present on the server.
        // Conveniently, extra data after ";" in the URL is included in request logs, but
        // ignored by the REST resource handler, which just returns the "Pulse" concept.
        final String urlPath = "/concept/" + Concepts.PULSE_UUID;
        List<String> params = new ArrayList<>();
        params.add("time=" + (new Date().getTime()));
        User user = App.getUserManager().getActiveUser();
        if (user != null) {
            params.add("user_id=" + user.id);
            if (user.isGuestUser()) {
                params.add("guest_user=1");
            }
        }
        for (int i = 0; i + 1 < pairs.size(); i += 2) {
            params.add(Utils.urlEncode(pairs.get(i)) + "=" + Utils.urlEncode(pairs.get(i + 1)));
        }

        LOG.i("Logging to server: %s", params);
        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                urlPath + ";" + Joiner.on(";").join(params), null, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                    }
                }, null);
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 0, 1));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void addPatient(AppPatientDelta patientDelta, final Response.Listener<Patient> patientListener,
            final Response.ErrorListener errorListener) {
        JSONObject json = new JSONObject();
        if (!patientDelta.toJson(json)) {
            throw new IllegalArgumentException("Unable to serialize the patient delta to JSON.");
        }

        LOG.v("Adding patient from JSON: %s", json.toString());

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails, "/patient", json,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            patientListener.onResponse(patientFromJson(response));
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                            errorListener.onErrorResponse(new VolleyError("Failed to parse response", e));
                        }
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void updatePatient(String patientUuid, AppPatientDelta patientDelta,
            final Response.Listener<Patient> patientListener, final Response.ErrorListener errorListener) {
        JSONObject json = new JSONObject();
        if (!patientDelta.toJson(json)) {
            throw new IllegalArgumentException("Unable to serialize the patient delta to JSON.");
        }

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                "/patient/" + patientUuid, json, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            patientListener.onResponse(patientFromJson(response));
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                            errorListener.onErrorResponse(new VolleyError("Failed to parse response", e));
                        }
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void addUser(final NewUser user, final Response.Listener<User> userListener,
            final Response.ErrorListener errorListener) {
        JSONObject requestBody = new JSONObject();
        try {
            requestBody.put("user_name", user.username);
            requestBody.put("given_name", user.givenName);
            requestBody.put("family_name", user.familyName);
            requestBody.put("password", user.password);

        } catch (JSONException e) {
            // This is almost never recoverable, and should not happen in correctly functioning code
            // So treat like NPE and rethrow.
            throw new RuntimeException(e);
        }

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails, "/user", requestBody,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            userListener.onResponse(userFromJson(response));
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                            errorListener.onErrorResponse(new VolleyError("Failed to parse response", e));
                        }
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void addEncounter(AppPatient patient, AppEncounter encounter,
            final Response.Listener<Encounter> encounterListener, final Response.ErrorListener errorListener) {
        JSONObject json = new JSONObject();
        if (!encounter.toJson(json)) {
            throw new IllegalArgumentException("Unable to serialize the encounter to JSON.");
        }

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails, "/patientencounters",
                json, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            encounterListener.onResponse(encounterFromJson(response));
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                            errorListener.onErrorResponse(new VolleyError("Failed to parse response", e));
                        }
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void getPatient(String patientId, final Response.Listener<Patient> patientListener,
            final Response.ErrorListener errorListener) {
        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                "/patient/" + patientId, null, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            patientListener.onResponse(patientFromJson(response));
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                            errorListener.onErrorResponse(new VolleyError("Failed to parse response", e));
                        }
                    }
                }, wrapErrorListener(errorListener));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void updatePatientLocation(String patientId, String newLocationId) {
        // TODO: Implement or remove (currently handled by updatePatient).
    }

    @Override
    public void listPatients(@Nullable String filterState, @Nullable String filterLocation,
            @Nullable String filterQueryTerm, final Response.Listener<List<Patient>> patientListener,
            Response.ErrorListener errorListener) {
        String query = filterQueryTerm != null ? filterQueryTerm : "";
        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                "/patient?q=" + Utils.urlEncode(query), null, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        ArrayList<Patient> patients = new ArrayList<>();
                        try {
                            JSONArray results = response.getJSONArray("results");
                            for (int i = 0; i < results.length(); i++) {
                                patients.add(patientFromJson(results.getJSONObject(i)));
                            }
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to convert JSON response");
                        }
                        patientListener.onResponse(patients);
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_VERY_LONG, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    private Patient patientFromJson(JSONObject object) throws JSONException {
        Patient patient = mGson.fromJson(object.toString(), Patient.class);

        if (!"M".equals(patient.gender) && !"F".equals(patient.gender)) {
            LOG.e("Invalid gender from server: " + patient.gender);
            patient.gender = "F";
        }
        return patient;
    }

    private Encounter encounterFromJson(JSONObject object) throws JSONException {
        Encounter encounter = mGson.fromJson(object.toString(), Encounter.class);

        return encounter;
    }

    @Override
    public void listUsers(@Nullable String filterQueryTerm, final Response.Listener<List<User>> userListener,
            Response.ErrorListener errorListener) {
        String query = filterQueryTerm != null ? filterQueryTerm : "";
        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                "/user?q=" + Utils.urlEncode(query), null, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        ArrayList<User> users = new ArrayList<>();
                        try {
                            JSONArray results = response.getJSONArray("results");
                            for (int i = 0; i < results.length(); i++) {
                                users.add(userFromJson(results.getJSONObject(i)));
                            }
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                        }
                        userListener.onResponse(users);
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_MEDIUM, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    private User userFromJson(JSONObject object) throws JSONException {
        return new User(object.getString("user_id"), object.getString("full_name"));
    }

    @Override
    public void addLocation(Location location, final Response.Listener<Location> locationListener,
            final Response.ErrorListener errorListener) {
        JSONObject requestBody;
        try {
            if (location.uuid != null) {
                throw new IllegalArgumentException("The server sets the uuids for new locations");
            }
            if (location.parent_uuid == null) {
                throw new IllegalArgumentException("You must set a parent_uuid for a new location");
            }
            if (location.names == null || location.names.isEmpty()) {
                throw new IllegalArgumentException("You must set a name in at least one locale for a new location");
            }
            requestBody = new JSONObject(mGson.toJson(location));
        } catch (JSONException e) {
            // This is almost never recoverable, and should not happen in correctly functioning code
            // So treat like NPE and rethrow.
            throw new RuntimeException(e);
        }

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails, "/location",
                requestBody, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        locationListener.onResponse(parseLocationJson(response));
                    }
                }, errorListener);
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void updateLocation(Location location, final Response.Listener<Location> locationListener,
            final Response.ErrorListener errorListener) {

        if (location.uuid == null) {
            throw new IllegalArgumentException("Location must be set for update " + location);
        }
        if (location.names == null || location.names.isEmpty()) {
            throw new IllegalArgumentException("New names must be set for update " + location);
        }
        JSONObject requestBody;
        try {
            requestBody = new JSONObject(mGson.toJson(location));
        } catch (JSONException e) {
            String msg = "Failed to write patient changes to Gson: " + location.toString();
            LOG.e(e, msg);
            errorListener.onErrorResponse(new VolleyError(msg));
            return;
        }

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                "/location/" + location.uuid, requestBody, new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        locationListener.onResponse(parseLocationJson(response));
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void deleteLocation(String locationUuid, final Response.ErrorListener errorListener) {
        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails,
                Request.Method.DELETE, "/location/" + locationUuid, null, null, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_SHORT, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    @Override
    public void listLocations(final Response.Listener<List<Location>> locationListener,
            Response.ErrorListener errorListener) {

        OpenMrsJsonRequest request = mRequestFactory.newOpenMrsJsonRequest(mConnectionDetails, "/location", null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        ArrayList<Location> result = new ArrayList<>();
                        try {
                            JSONArray results = response.getJSONArray("results");
                            for (int i = 0; i < results.length(); i++) {
                                Location location = parseLocationJson(results.getJSONObject(i));
                                result.add(location);
                            }
                        } catch (JSONException e) {
                            LOG.e(e, "Failed to parse response");
                        }
                        locationListener.onResponse(result);
                    }
                }, wrapErrorListener(errorListener));
        request.setRetryPolicy(new DefaultRetryPolicy(Common.REQUEST_TIMEOUT_MS_MEDIUM, 1, 1f));
        mConnectionDetails.getVolley().addToRequestQueue(request);
    }

    private Location parseLocationJson(JSONObject object) {
        return mGson.fromJson(object.toString(), Location.class);
    }

    @Override
    public void cancelPendingRequests() {
        // TODO: Implement or deprecate. The way this was implemented before, where a string
        // was the tag, is not safe. Only the class that initiated a request (and its delegates)
        // should be able to cancel that request.
    }
}