Java tutorial
/** * 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(); } } }