de.uni_koblenz_landau.apow.helper.SyncHelper.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_koblenz_landau.apow.helper.SyncHelper.java

Source

/**
 * Apow - a mobile EHR Management System for low-resource environments
 * in developing countries, exemplified by rural Ghana
 * Copyright (C) 2014 Martin Landua
 *
 * 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 http://www.gnu.org/licenses/.
 */

package de.uni_koblenz_landau.apow.helper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import de.uni_koblenz_landau.apow.db.SyncDBHelper;
import de.uni_koblenz_landau.apow.db.UserDAO;
import de.uni_koblenz_landau.apow.helper.Constants.FACTTABLES;
import de.uni_koblenz_landau.apow.helper.Constants.TABLES;
import android.content.ContentValues;
import android.content.Context;
import android.util.Log;

/**
 * Helper for syncing the local database with a database server.
 * @author Martin Landua
 *
 */
public class SyncHelper {

    private static final HttpClient client = getNewHttpClient();

    /**
     * Fetches user tables from server and writes the data to the local database.
     * @param context Context
     * @return Success?
     * @throws IOException
     * @throws JSONException
     */
    public static Boolean fetchUserTables(Context context) throws IOException, JSONException {

        // Get user credentials.
        UserDAO dao1 = new UserDAO(context);
        dao1.open(context);
        ListViewItem settings = dao1.getSettings();
        dao1.close();
        String username = settings.getField1();
        String password = settings.getField2();

        SyncDBHelper dao = new SyncDBHelper(context);
        dao.open(context);

        // Update person.
        String table = TABLES.PERSON.getName();
        String lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        List<ContentValues> values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePersons(values);
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update patient.
        table = TABLES.PATIENT.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePatients(values);
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update person_address.
        table = TABLES.PERSON_ADDRESS.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePersonChildren(values, table, "person_id");
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update person_name.
        table = TABLES.PERSON_NAME.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePersonChildren(values, table, "person_id");
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update patient_identifier.
        table = TABLES.PATIENT_IDENTIFIER.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePersonChildren(values, table, "patient_id");
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update  encounter.
        table = TABLES.ENCOUNTER.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updatePersonChildren(values, table, "patient_id");
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        // Update obs.
        table = TABLES.OBS.getName();
        lastUpdate = dao.lastUpdate(table);
        Log.i("sync", "Starting Sync of table: " + table + ", syncdate = " + lastUpdate);
        values = getContentValues(table, lastUpdate, username, password);
        if (values != null) {
            int count = dao.updateObs(values);
            Log.i("sync", "Fetched " + count + " rows in table " + table);
        } else {
            Log.i("sync", "Error: Update in table " + table);
            dao.close();
            return false;
        }

        dao.close();
        return true;
    }

    /**
     * Fetches fact tables from server and writes the data to the local database.
     * @param lastUpdate Datetime of last update
      * @param context Context
     * @return Success?
     * @throws IllegalStateException
     * @throws IOException
     * @throws JSONException
     */
    public static Boolean fetchFactTables(String lastUpdate, Context context)
            throws IllegalStateException, IOException, JSONException {

        // If last update is empty or the last update was 3 days ago, fetch tables.
        if ("".equals(lastUpdate) || ((new Date()).getTime() - Helper.stringToDate(lastUpdate).getTime())
                / (1000 * 60 * 60 * 24) > Constants.SYNC_INTERVAL_DAYS) {

            // Get user credentials.
            UserDAO dao1 = new UserDAO(context);
            dao1.open(context);
            ListViewItem settings = dao1.getSettings();
            dao1.close();
            String username = settings.getField1();
            String password = settings.getField2();

            // Sync all fact tables.
            SyncDBHelper dao = new SyncDBHelper(context);
            dao.open(context);
            for (FACTTABLES tableconstant : Constants.FACTTABLES.values()) {
                String table = tableconstant.getName();
                Log.i("sync", "Starting sync of table: " + table);

                List<ContentValues> values = getContentValues(table, lastUpdate, username, password);
                if (values != null) {
                    int count = dao.bulkInsert(values, table);
                    Log.i("sync", "Updated " + count + " rows in table " + table);
                } else {
                    Log.i("sync", "Error: Update in table " + table);
                    dao.close();
                    return false;
                }
            }
            dao.close();
        }
        return true;
    }

    /**
     * Fetches all locations from server.
     * @return List of locations.
     * @throws IllegalStateException
     * @throws IOException
     * @throws JSONException
     */
    public static List<ListViewItem> fetchLocations() throws IllegalStateException, IOException, JSONException {
        // Prepare request.
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair(Constants.PARAM_ACTION, Constants.PARAM_ACTION_LOCATIONS));
        HttpPost post = new HttpPost(Constants.BASE_URL + Constants.PARAM_TYPE_INDEX);
        post.setEntity(new UrlEncodedFormEntity(params));

        // Execute request.
        HttpResponse response = client.execute(post);
        int statusCode = response.getStatusLine().getStatusCode();

        // If response is valid and not empty, read data and parse JSON.
        if (statusCode == HttpStatus.SC_OK) {
            HttpEntity entity = response.getEntity();
            String responseString = readStream(entity.getContent());
            if (responseString != null) {
                List<ListViewItem> result = new ArrayList<>();
                JSONObject obj = new JSONObject(responseString);
                JSONArray array = obj.getJSONArray(Constants.PARAM_ACTION_LOCATIONS);
                for (int i = 0; i < array.length(); i++) {
                    JSONObject item = array.getJSONObject(i);
                    result.add(new ListViewItem(item.getString("name"), item.getInt("location_id")));
                }
                return result;
            }
        }
        return null;
    }

    /**
     * Fetches a userID from server, if credentials are valid.
     * @param username User name
     * @param password Password
     * @return if valid userID, else -1
     */
    public static int fetchUser(String username, String password) {
        // Prepare request.
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair(Constants.PARAM_ACTION, Constants.PARAM_ACTION_LOGIN));
        params.add(new BasicNameValuePair(Constants.PARAM_USERNAME, username));
        params.add(new BasicNameValuePair(Constants.PARAM_PASSWORD, password));
        HttpPost post = new HttpPost(Constants.BASE_URL + Constants.PARAM_TYPE_INDEX);
        try {
            // Execute request.
            post.setEntity(new UrlEncodedFormEntity(params));
            HttpResponse response = client.execute(post);
            int statusCode = response.getStatusLine().getStatusCode();
            // If response is valid and not empty, read data and parse JSON.
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                String responseString;
                responseString = readStream(entity.getContent());
                if (responseString != null) {
                    JSONObject obj = new JSONObject(responseString);
                    if (obj.getBoolean("success")) {
                        obj.getInt("user_id");
                        return obj.getInt("user_id");
                    }
                    return -1;
                }
            }
        } catch (IllegalStateException | IOException | JSONException e) {
            e.printStackTrace();
            return -1;
        }
        return -1;
    }

    /**
     * Uploads user tables to server, and updates local data with change dates.
      * @param context Context
     * @throws JSONException
     * @throws IOException
     */
    public static void postUserTables(Context context) throws JSONException, IOException {

        // Get user credentials.
        UserDAO dao = new UserDAO(context);
        dao.open(context);
        ListViewItem settings = dao.getSettings();
        dao.close();
        String username = settings.getField1();
        String password = settings.getField2();

        // Upload data of tables.
        postUserTableData(TABLES.PERSON.getName(), context, username, password);
        postUserTableData(TABLES.PATIENT.getName(), context, username, password);
        postUserTableData(TABLES.PATIENT_IDENTIFIER.getName(), context, username, password);
        postUserTableData(TABLES.PERSON_ADDRESS.getName(), context, username, password);
        postUserTableData(TABLES.PERSON_NAME.getName(), context, username, password);
        postUserTableData(TABLES.ENCOUNTER.getName(), context, username, password);
        postUserTableData(TABLES.OBS.getName(), context, username, password);
    }

    /**
     * Uploads data for a given table to server and updates local data with change dates.
     * @param table given table.
      * @param context Context
     * @param username User name
      * @param password Password
     * @throws JSONException
     * @throws IOException
     */
    private static void postUserTableData(String table, Context context, String username, String password)
            throws JSONException, IOException {
        SyncDBHelper dao = new SyncDBHelper(context);
        dao.open(context);
        // Get rows with changes.
        List<JSONObject> items = dao.getDirtyRows(table);
        for (JSONObject item : items) {
            item.put(Constants.PARAM_USERNAME, username);
            item.put(Constants.PARAM_PASSWORD, password);
            // Upload row, if it is successful, update local row.
            String response = postJSON(table, item);
            if (response != null) {
                Log.i("sync", response);
                JSONObject obj = new JSONObject(response);
                if (obj.getBoolean("success")) {
                    //Update local row.
                    String uuid = item.getJSONObject("values").getString("uuid");
                    String datechanged = "";
                    if (table.equals(Constants.TABLES.OBS.getName())) {
                        datechanged = obj.getJSONObject("values").getString("date_changed");
                    }
                    String datecreated = obj.getJSONObject("values").getString("date_created");
                    dao.cleanPerson(table, uuid, datechanged, datecreated);
                }

            }
        }
        dao.close();
    }

    /**
     * Uploads a row as JSONObject.
     * @param table Given table.
     * @param obj Given JSONObject.
     * @return Response
     * @throws IOException
     */
    private static String postJSON(String table, JSONObject obj) throws IOException {
        // Prepare request.
        HttpPost post = new HttpPost(Constants.BASE_URL + Constants.PARAM_TYPE_POST);
        post.setEntity(new StringEntity(obj.toString(), HTTP.UTF_8));
        post.setHeader("Accept", "application/json");
        post.setHeader("Content-type", "application/json");
        // Execute request.
        HttpResponse response = client.execute(post);
        int statusCode = response.getStatusLine().getStatusCode();
        // If response is valid, convert to string.
        if (statusCode == HttpStatus.SC_OK) {
            HttpEntity entity = response.getEntity();
            return readStream(entity.getContent());
        } else {
            return null;
        }
    }

    /**
     * Fetches data of a table from server and converts it to SQLite content values.
     * @param table Table
     * @param lastUpdate Last update
     * @param username User name
     * @param password Password
     * @return Content values of data
     * @throws IOException
     * @throws JSONException
     */
    private static List<ContentValues> getContentValues(String table, String lastUpdate, String username,
            String password) throws IOException, JSONException {
        // Fetch table data as JSONObject
        String responseString = fetchFactTableData(table, lastUpdate, username, password);
        if (responseString != null) {
            // Parse JSON.
            JSONObject obj = new JSONObject(responseString);
            JSONArray array = obj.getJSONArray(table);
            // Convert to ContentValues.
            return JSONArrayToList(array);
        }
        return null;
    }

    /**
     * Fetches data of a fact table from server. 
     * @param table Table
     * @param lastUpdate Last update
     * @param username User name
     * @param password Password
     * @return Response string.
     * @throws IOException
     */
    private static String fetchFactTableData(String table, String lastUpdate, String username, String password)
            throws IOException {
        // Prepare request.
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair(Constants.PARAM_DATE, lastUpdate));
        params.add(new BasicNameValuePair(Constants.PARAM_TABLE, table));
        params.add(new BasicNameValuePair(Constants.PARAM_USERNAME, username));
        params.add(new BasicNameValuePair(Constants.PARAM_PASSWORD, password));
        HttpPost post = new HttpPost(Constants.BASE_URL + Constants.PARAM_TYPE_FETCH);
        post.setEntity(new UrlEncodedFormEntity(params));

        // Execute request.
        HttpResponse response = client.execute(post);
        int statusCode = response.getStatusLine().getStatusCode();

        // If response is not empty and valid, convert response to string.
        if (statusCode == HttpStatus.SC_OK) {
            HttpEntity entity = response.getEntity();
            return readStream(entity.getContent());
        } else {
            return null;
        }
    }

    /**
     * Converts a JSONArray to a list of content values.
     * @param array JSONArray
     * @return List of content values.
     * @throws JSONException
     */
    private static List<ContentValues> JSONArrayToList(JSONArray array) throws JSONException {
        List<ContentValues> result = new ArrayList<>();
        ContentValues item;

        if (array.length() != 0) {
            for (int i = 0; i < array.length(); i++) {
                JSONObject obj;
                obj = (JSONObject) array.get(i);
                item = JSONToContentValues(obj);
                result.add(item);
            }
        }
        return result;
    }

    /**
     * Converts a JSONObject to a content value.
     * @param obj JSONObject
     * @return Content value
     * @throws JSONException
     */
    private static ContentValues JSONToContentValues(JSONObject obj) throws JSONException {

        ContentValues values = new ContentValues();
        Iterator<?> keys = obj.keys();
        // Loop through all entries.
        while (keys.hasNext()) {
            String key = (String) keys.next();

            // Check for data type and insert in the right format.
            if (!obj.isNull(key) || "".equals(obj.getString(key))) {
                Object a = obj.get(key);
                if (a instanceof Integer) {
                    values.put(key, (int) a);
                }
                if (a instanceof Double) {
                    values.put(key, (double) a);
                }
                if (a instanceof String) {
                    values.put(key, (String) a);
                }
            }
        }
        return values;
    }

    /**
     * Reads Stream into string.
     * @param stream Input stream
     * @return String
     * @throws IOException
     */
    private static String readStream(InputStream stream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        StringBuilder builder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }
        return builder.toString();
    }

    /**
     * Creates a HTTPClient for usage with self signed SSL certificates.
     * 
     * Sources:
     * http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https
     * http://havrl.blogspot.de/2013/08/synchronization-algorithm-for.html 
     * 
     * @return HTTPClient
     */
    private static HttpClient getNewHttpClient() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);

            SSLSocketFactory sf = new SelfSignedSSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", sf, 443));

            ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

            return new DefaultHttpClient(ccm, params);
        } catch (Exception e) {
            return new DefaultHttpClient();
        }
    }
}