Java tutorial
/***************************************************************** DataOutHandler Copyright (C) 2011-2013 The National Center for Telehealth and Technology Eclipse Public License 1.0 (EPL-1.0) This library is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License as published by the Free Software Foundation, version 1.0 of the License. The Eclipse Public License is a reciprocal license, under Section 3. REQUIREMENTS iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. Post your updates and modifications to our GitHub or email to t2@tee2.org. This library is distributed WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License 1.0 (EPL-1.0) for more details. You should have received a copy of the Eclipse Public License along with this library; if not, visit http://www.opensource.org/licenses/EPL-1.0 *****************************************************************/ package com.t2.dataouthandler; import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.Vector; import org.apache.http.cookie.Cookie; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.JsonNodeFactory; import org.codehaus.jackson.node.ObjectNode; import org.joda.time.DateTime; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.t2health.lib1.LogWriter; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.preference.PreferenceManager; import android.text.format.Time; import android.util.Log; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.dynamodb.AmazonDynamoDBClient; import com.amazonaws.services.dynamodb.model.AttributeValue; import com.amazonaws.services.dynamodb.model.PutItemRequest; import com.amazonaws.tvmclient.AmazonClientManager; import com.janrain.android.engage.JREngage; import com.janrain.android.engage.JREngageDelegate; import com.janrain.android.engage.JREngageError; import com.janrain.android.engage.net.async.HttpResponseHeaders; import com.janrain.android.engage.types.JRActivityObject; import com.janrain.android.engage.types.JRDictionary; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.JsonHttpResponseHandler; import com.loopj.android.http.PersistentCookieStore; import com.loopj.android.http.RequestParams; import com.t2.aws.DynamoDBManager; import com.t2.dataouthandler.DataOutHandler; import com.t2.dataouthandler.DataOutHandlerException; import com.t2.dataouthandler.DataOutHandlerTags; import com.t2.dataouthandler.GUIHelper.LoginResult; import com.t2.dataouthandler.T2AuthDelegate; import com.t2.dataouthandler.dbcache.DbCache; import com.t2.dataouthandler.dbcache.SqlPacket; import com.t2.drupalsdk.DrupalRegistrationResponse; import com.t2.drupalsdk.DrupalUtils; import com.t2.drupalsdk.ServicesClient; import com.t2.drupalsdk.UserServices; import com.t2.h2h4h.Checkin; import com.t2.h2h4h.DBObject; import com.t2.h2h4h.Habit; /** * Handles interface to external databases. * Also initializes Authentication services (JanRain) * * @author scott.coleman * */ public class DataOutHandler implements JREngageDelegate { private static final String TAG = DataOutHandler.class.getName(); private static final String VERSION_STRING = "2.3.2"; //private static final String DEFAULT_REST_DB_URL = "http://gap.t2health.org/and/phpWebservice/webservice2.php"; // private static final String DEFAULT_REST_DB_URL = "http://gap.t2health.org/and/json.php"; private static final String DEFAULT_REST_DB_URL = "http://ec2-50-112-197-66.us-west-2.compute.amazonaws.com/mongo/json.php"; private static final String DEFAULT_AWS_DB_URL = "h2tvm.elasticbeanstalk.com"; // private static final String DEFAULT_DRUPAL_DB_URL = "http://t2health.us/h2/android/"; private static final String DEFAULT_DRUPAL_DB_URL = "http://t2health.us/h4hnew/api/"; private String fred = "{ \"type\": \"check_in\", \"habit_id\": \"23\", \"uid\": \"44\", \"title\": \"new checkin blah blah blah 2dsfsdf nid 99 unset\", \"log\": \"\", \"status\": \"1\", \"comment\": \"2\", \"promote\": \"0\", \"sticky\": \"0\", \"type\": \"check_in\", \"language\": \"und\", \"created\": \"1376957648\", \"changed\": \"1376957648\", \"tnid\": \"0\", \"translate\": \"0\", \"revision_timestamp\": \"1376957648\", \"revision_uid\": \"44\", \"body\": { \"und\": [{ \"value\": \"\", \"summary\": \"\", \"format\": \"filtered_html\", \"safe_value\": \"\", \"safe_summary\": \"\" }] }, \"field_checkin_time\": {\"und\": [ { \"value\": { \"date\": \"2013-08-20 13:31\"} } ] }}"; private static final int INDEX_DRUPAL_SERVICE = 1; private static final int INDEX_DRUPAL_REST_ENDPOINT = 2; private static final int MIN_PARAMETERS = 3; // Database types. // Note that different database types // may need different processing and even // different structures, thus is it important to // use DataOutPacket structure to add data public final static int DATABASE_TYPE_AWS = 0; // AWS (Goes to AWS DynamoDB) public final static int DATABASE_TYPE_T2_REST = 1; // T2 Rest server (goes to Mongo DB) public final static int DATABASE_TYPE_T2_DRUPAL = 2; // T2 Drupal - goes to a Drupal database public final static int DATABASE_TYPE_NONE = -1; private static final boolean USE_SSL = false; private static final boolean VERBOSE_LOGGING = true; private static String ENGAGE_APP_ID = "khekfggiembncbadmddh"; public static final String SHORT_TIME_STAMP = "\"TS\""; public static final String DATA_TYPE_RATING = "RatingData"; public static final String DATA_TYPE_INTERNAL_SENSOR = "InternalSensor"; public static final String DATA_TYPE_EXTERNAL_SENSOR = "ExternalSensor"; public static final String DATA_TYPE_USER_ENTERED_DATA = "UserEnteredData"; // public static final int SYNC_TIMEOUT = 200000; public static final int SYNC_TIMEOUT = 60000; // 60 seconds public boolean mLogCatEnabled = false; public boolean mLoggingEnabled = false; private boolean mDatabaseEnabled = false; private boolean mSessionIdEnabled = false; /** * Whether or not to user Drupal user id when filtering for all * records */ private boolean mFilterQueriesOnUserId = true; private boolean mRequiresCSRF = false; private String mCSRFToken = ""; public void filterQueriesOnUserId(boolean filterQueriesOnUserId) { mFilterQueriesOnUserId = filterQueriesOnUserId; } public void setRequiresCSRF(boolean requiresCSRF) { mRequiresCSRF = requiresCSRF; } private List<DBObject> mDBObjects = new ArrayList<DBObject>(); public void registerDbObject(DBObject object) { mDBObjects.add(object); } /** * Progress dialog used for traditional authentication */ private ProgressDialog mProgressDialog; /// Object tokens for Synchronizing calls to certain routines private Object addPacketToCacheSyncToken = new Object(); private Object updateCacheSyncToken = new Object(); private Object sendPacketToRemoteDbToken = new Object(); /** * Selects whether or not to show a traditional login alongside Janrain Social logins. * * Traditional login speaks directly to the Drupal server. */ private boolean mAllowTraditionalLogin = true; /** * Signifies that user was logged in using traditional login (not janrain) */ private boolean mLoggedInAsTraditional = false; /** * User identification to be associated with data stored */ public String mUserId = ""; /** * Date a particular session started, there can be multiple data * saves for any session */ public String mSessionDate = ""; /** * Name of calling application (logged with data) */ public String mAppName = ""; /** * Source type of data (internal, external, etc.) */ public String mDataType = ""; /** * Used to write data logs */ private LogWriter mLogWriter; /** * Context of calling party */ private Context mContext; /** * Desired format of data lof files */ private int mLogFormat = GlobalH2.LOG_FORMAT_JSON; // Alternatively LOG_FORMAT_FLAT /** * ID of a particular session (for multiple sessions in an application run */ private long mSessionId; /** * URL of the remote database we are saving to */ String mRemoteDatabase; /** * Thread used to communicate messages in background to server */ private DispatchThread mDispatchThread = null; /** * Application version info determined by the package manager */ private String mApplicationVersion = ""; /** * Engage App ID - Supplied by JanRain */ String mEngageAppId = ENGAGE_APP_ID; /** * Token URL used for Janrain/Drupal integration */ String mEngageTokenUrl = ""; /** * Engage instance for openID authentication */ private JREngage mEngage; /** * JanRain Callbacks for notification of auth success/fail, etc. */ private T2AuthDelegate mT2AuthDelegate; /** * Contains information about authenticated user */ private JRDictionary mAuth_info; /** * The provider the user used to authenticate with (provided by JanRain) */ private String mAuthProvider; // T2 Drupal stuff /** * Used to save Drupal session cookies for authentication. */ private PersistentCookieStore mCookieStore; /** * HTTP services client used to talk to Drupal. */ private ServicesClient mServicesClient; /** * True if this module has successfully authenticated a user. */ private boolean mAuthenticated = false; /** * Set this to true to require authtication for all database puts. */ private boolean mRequiresAuthentication = true; /** * Database manager when sending data to external Amazon database */ public static AmazonClientManager sClientManager = null; /** * sets which type of external database is setup and used */ private int mDatabaseType; /** * Sets the AWS table name into which data is stored (AWS only) */ private String mAwsTableName = "TestT2"; // Default to TestT2 /** * Shared preferences for this lib (will be the same as calling party) */ SharedPreferences mSharedPreferences; /** * Static instance of this class */ private static DataOutHandler sDataOutHandler; /** * Session cookie Janrain Obtains from Drupal for active session * This is used to communicate the login info to Drupal */ private Cookie drupalSessionCookie; /** * List of Drupal node id's currently existing in Drupal */ private List<String> mRemoteDrupalNodeIdList; /** * List of node ids which we have reuqested to be deleted from Drupal */ private List<String> mNodeDeleteQueue = new ArrayList<String>(); /** * Database cache */ public DbCache mDbCache; /** * Saved instance for this module */ private DataOutHandler mInstance; /** * Listener for database cache events (Updates, etc) */ private DatabaseCacheUpdateListener mDatabaseUpdateListener; /** * Currently logged in drupal user */ private String mDrupalUserId = ""; /** * Sets the database listener * @param mDatabaseUpdateListener */ public void setDatabaseUpdateListener(DatabaseCacheUpdateListener mDatabaseUpdateListener) { this.mDatabaseUpdateListener = mDatabaseUpdateListener; } /** * Sets the AWS table name (applicable only if AWS database is chosen) * @param awsTableName Name of the table */ public void setAwsTableName(String awsTableName) { this.mAwsTableName = awsTableName; } /** * Returns version of this package * @return */ public static String getVersion() { return VERSION_STRING; } /** * Retrieves a static instance of DataOutHandler * * @param context - Android context of calling party * @param userId - Used id * @param sessionDate - Date of the session * @param appName - Application name * @param dataType - data type to store * @param sessionId - Session Id * @return Static instance of dataOutHandler */ public synchronized static DataOutHandler getInstance(Context context, String userId, String sessionDate, String appName, String dataType, long sessionId) { if (sDataOutHandler == null) { sDataOutHandler = new DataOutHandler(context, userId, sessionDate, appName, dataType, sessionId); } return sDataOutHandler; } /** * Retrieves a static instance of DataOutHandler * * @return Static instance of dataOutHandler * @throws DataOutHandlerException */ public static DataOutHandler getInstance() throws DataOutHandlerException { if (sDataOutHandler == null) { throw new DataOutHandlerException("DataOutHandler has not been initialized"); } return sDataOutHandler; } /** * Constructor. Sets up context and user/session parameters * * @param context - Context of calling activity * @param userId - User ID detected by calling activity * @param sessionDate - Session date created by the calling activity (data/time stamp) * @param appName - Name of calling application */ public DataOutHandler(Context context, String userId, String sessionDate, String appName) { mAppName = appName; mContext = context; mUserId = userId; mSessionDate = sessionDate; mSessionIdEnabled = false; mInstance = this; } /** * Constructor. sets up context and user/session parameters * * @param context - Context of calling activity * @param userId - User ID detected by calling activity * @param sessionDate - Session date created by the calling activity (data/time stamp) * @Param appName - Name of calling application (for logging) * @Param dataType - Data type (Internal or external) * @param sessionId - long session ID to be included in all packets */ public DataOutHandler(Context context, String userId, String sessionDate, String appName, String dataType, long sessionId) { mAppName = appName; mDataType = dataType; mContext = context; mUserId = userId; mSessionDate = sessionDate; mSessionIdEnabled = true; mSessionId = sessionId; mInstance = this; } /** * Disables database functionality */ public void disableDatabase() { mDatabaseEnabled = false; } /** * Enables database functionality */ public void enableDatabase() { mDatabaseEnabled = true; } /** * Sets formatting of log output * LOG_FORMAT_JSON = 1; JSON format * LOG_FORMAT_FLAT = 2; Standard column format * * @param format - format to use */ public void setLogFormat(int format) { mLogFormat = format; } /** * @author scott.coleman * Task to check the status of an AWS database table */ class CheckTableStatusTask extends AsyncTask<String, Void, String> { private Exception exception; protected String doInBackground(String... urls) { try { String tableStatus = DynamoDBManager.getTestTableStatus(); String status = tableStatus; return status; } catch (Exception e) { this.exception = e; return ""; } } protected void onPostExecute(String status) { Log.d(TAG, "Database status = " + status); } } /** * Sets the RequiresAuthentication flag * @param mRequiresAuthentication true/false */ public void setRequiresAuthentication(boolean mRequiresAuthentication) { this.mRequiresAuthentication = mRequiresAuthentication; } /** * Initialized specified database * * @param remoteDatabase URL of database to send data to. * @param databaseType Type of database (AWS, TRest, T2Drupal, etc.). * @param t2AuthDelegate Callbacks to send status to. * @throws DataOutHandlerException * @throws MalformedURLException */ public void initializeDatabase(String remoteDatabase, String databaseType) throws DataOutHandlerException, MalformedURLException { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); // Do it this way for backward compatibility mSharedPreferences.edit().putString("external_database_type", databaseType); initializeDatabase("", "", "", "", remoteDatabase); } /** * Initialized specified database * * @param remoteDatabase URL of database to send data to. * @param databaseType Type of database (AWS, TRest, T2Drupal, etc.). * @param t2AuthDelegate Callbacks to send status to. * @throws DataOutHandlerException * @throws MalformedURLException */ public void initializeDatabase(String remoteDatabase, String databaseType, T2AuthDelegate t2AuthDelegate) throws DataOutHandlerException, MalformedURLException { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); // Do it this way for backward compatibility mSharedPreferences.edit().putString("external_database_type", databaseType); mT2AuthDelegate = t2AuthDelegate; initializeDatabase("", "", "", "", remoteDatabase); } /** * @param remoteDatabase URL of database to send data to. * @param databaseType Type of database (AWS, TRest, T2Drupal, etc.) * @param t2AuthDelegate Callbacks to send status to. * @param awsTableName AWS table name to use when putting data. * @throws DataOutHandlerException * @throws MalformedURLException */ public void initializeDatabase(String remoteDatabase, String databaseType, T2AuthDelegate t2AuthDelegate, String awsTableName) throws DataOutHandlerException, MalformedURLException { mAwsTableName = awsTableName; mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); // Do it this way for backward compatibility mSharedPreferences.edit().putString("external_database_type", databaseType); mT2AuthDelegate = t2AuthDelegate; initializeDatabase("", "", "", "", remoteDatabase); } /** * Initializes the current database * * Note that all of the parameters (with the exception of remoteDatabase) sent to this routine are for CouchDB only. * Currently they are all N/A * * Endpoint for all initialize variants. * * @param databaseName N/A Local SQLITE database name * @param designDocName N/A Design document name * @param designDocId N/A Design document ID * @param viewName N/AView associated with database * @param remoteDatabase Name of external database * @throws DataOutHandlerException * @throws MalformedURLException */ public void initializeDatabase(String databaseName, String designDocName, String designDocId, String viewName, String remoteDatabase) throws DataOutHandlerException, MalformedURLException { mDatabaseEnabled = true; // Set database type mDatabaseType = DATABASE_TYPE_NONE; // Get chosen database from preferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); String databaseTypeString = mSharedPreferences.getString("external_database_type", "AWS"); // Based on database type: // Set up mRemoteDatabase based on either remoteDatabase if it's not blank, // or default values based on database type // Then do any database type specific initialization if (databaseTypeString.equalsIgnoreCase("AWS")) { Log.d(TAG, "Using AWS Database type"); mDatabaseType = DATABASE_TYPE_AWS; if (remoteDatabase != null) { if (remoteDatabase.equalsIgnoreCase("")) { mRemoteDatabase = DEFAULT_AWS_DB_URL; } else { mRemoteDatabase = remoteDatabase; } // // Note: for AWS we don't supply a token URL, thats // // only for interfacing with Drupal // mEngage = JREngage.initInstance(mContext, mEngageAppId, "", this); // // // This is to account for a bug in janrain where a delegate might not get added in the initinstance call // // As odd as it seems, this ensures that only one delegate gets added per instance. // mEngage.removeDelegate(this); // mEngage.addDelegate(this); // // JREngage.blockOnInitialization(); // clientManager = new AmazonClientManager(mContext.getSharedPreferences("com.amazon.aws.demo.AWSDemo", Context.MODE_PRIVATE), mRemoteDatabase); sClientManager = new AmazonClientManager(mSharedPreferences, mRemoteDatabase); // TBD - we should probably check the table status //new CheckTableStatusTask().execute(""); } } else if (databaseTypeString.equalsIgnoreCase("T2REST")) { Log.d(TAG, "Using T2 Rest Database type"); mDatabaseType = DATABASE_TYPE_T2_REST; if (remoteDatabase != null) { if (remoteDatabase.equalsIgnoreCase("")) { mRemoteDatabase = DEFAULT_REST_DB_URL; } else { mRemoteDatabase = remoteDatabase; } // mEngage = JREngage.initInstance(mContext, mEngageAppId, mEngageTokenUrl, this); // // This is to account for a bug in janrain where a delegate might not get added in the initinstance call // // As odd as it seems, this ensures that only one delegate gets added per instance. // mEngage.removeDelegate(this); // mEngage.addDelegate(this); // // JREngage.blockOnInitialization(); } } else if (databaseTypeString.equalsIgnoreCase("T2DRUPAL")) { Log.d(TAG, "Using T2 Drupal Database type"); mDatabaseType = DATABASE_TYPE_T2_DRUPAL; initializeDrupalDatabaseNames(remoteDatabase); // mEngage = JREngage.initInstance(mContext, mEngageAppId, mEngageTokenUrl, this); // // This is to account for a bug in janrain where a delegate might not get added in the initinstance call // // As odd as it seems, this ensures that only one delegate gets added per instance. // mEngage.removeDelegate(this); // mEngage.addDelegate(this); // // JREngage.blockOnInitialization(); try { mServicesClient = new ServicesClient(mRemoteDatabase); } catch (MalformedURLException e1) { Log.e(TAG, e1.toString()); e1.printStackTrace(); } catch (DataOutHandlerException e1) { Log.e(TAG, e1.toString()); e1.printStackTrace(); } mCookieStore = new PersistentCookieStore(mContext); mCookieStore.clear(); // Make sure to start fresh mServicesClient.setCookieStore(mCookieStore); try { mDbCache = new DbCache(mRemoteDatabase, mContext, mDatabaseUpdateListener); } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } // Make sure a valid database was selected if (mDatabaseType == DATABASE_TYPE_NONE) { throw new DataOutHandlerException("Invalid database type"); } // Now do any global database (ot other) initialization Log.d(TAG, "Initializing T2 database dispatcher"); Log.d(TAG, "Remote database name = " + mRemoteDatabase); mDispatchThread = new DispatchThread(); mDispatchThread.start(); } public DataOutPacket getPacketByRecordId(String recordId) throws DataOutHandlerException { SqlPacket sqlPacket = mDbCache.getPacketByRecordId(recordId); DataOutPacket doPacket = new DataOutPacket(sqlPacket); return doPacket; } public DataOutPacket getPacketByDrupalId(String drupalId) throws DataOutHandlerException { SqlPacket sqlPacket = mDbCache.getPacketByDrupalId(drupalId); DataOutPacket doPacket = new DataOutPacket(sqlPacket); return doPacket; } /** * Formats mRemoteDatabase, and mEngageTokenUrl with proper database names (with defaults if blank) * @param remoteDatabase Remote database name * @throws DataOutHandlerException * @throws MalformedURLException */ void initializeDrupalDatabaseNames(String remoteDatabase) throws DataOutHandlerException, MalformedURLException { if (remoteDatabase.equalsIgnoreCase("")) { remoteDatabase = DEFAULT_DRUPAL_DB_URL; } URL url = new URL(remoteDatabase); String protocol = url.getProtocol(); String host = url.getHost(); String path = url.getPath(); String[] pathTokens = path.split("/"); String drupalRestEndpoint = ""; String drupalService = ""; if (pathTokens.length == MIN_PARAMETERS) { drupalService = pathTokens[INDEX_DRUPAL_SERVICE]; drupalRestEndpoint = pathTokens[INDEX_DRUPAL_REST_ENDPOINT]; } else { throw new DataOutHandlerException("Remote database URL incorrectly formatted - " + "must include Drupal service and Drupal Rest Endpoint"); } if (USE_SSL) { mRemoteDatabase = "https://" + host + "/" + drupalService + "/" + drupalRestEndpoint; mEngageTokenUrl = "https://" + host + "/" + drupalService + "/rpx/token_handler?destination=node"; } else { mRemoteDatabase = "http://" + host + "/" + drupalService + "/" + drupalRestEndpoint; mEngageTokenUrl = "http://" + host + "/" + drupalService + "/rpx/token_handler?destination=node"; } } /** * Initializes the current database * * All new users should use this entry point for initializing the database * * @param remoteDatabase Name of remote database (URI) to use * @param mDatabaseType Database type (integer) (See DATABASE_TYPE_xxx) * @param t2AuthDelegate t2AuthDelegate Callbacks to send status to. * @throws DataOutHandlerException */ public void initializeDatabase(String remoteDatabase, int databaseType, T2AuthDelegate t2AuthDelegate) throws DataOutHandlerException, MalformedURLException { mDatabaseType = databaseType; mT2AuthDelegate = t2AuthDelegate; if (remoteDatabase == null) { throw new DataOutHandlerException("remoteDatabase must not be null"); } // Make sure a valid database was selected if (mDatabaseType != this.DATABASE_TYPE_T2_DRUPAL) { throw new DataOutHandlerException("Database type invalid or not supported at this time"); } // TODO: re-add support for T2Rest and AWS initializeDrupalDatabaseNames(remoteDatabase); // mEngage = JREngage.initInstance(mContext, mEngageAppId, mEngageTokenUrl, this); // // This is to account for a bug in janrain where a delegate might not get added in the initinstance call // // As odd as it seems, this ensures that only one delegate gets added per instance. // mEngage.removeDelegate(this); // mEngage.addDelegate(this); // // JREngage.blockOnInitialization(); try { mServicesClient = new ServicesClient(mRemoteDatabase); } catch (MalformedURLException e1) { Log.e(TAG, e1.toString()); e1.printStackTrace(); } catch (DataOutHandlerException e1) { Log.e(TAG, e1.toString()); e1.printStackTrace(); } mCookieStore = new PersistentCookieStore(mContext); mCookieStore.clear(); // Make sure to start fresh mServicesClient.setCookieStore(mCookieStore); try { mDbCache = new DbCache(mRemoteDatabase, mContext, mDatabaseUpdateListener); } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new DataOutHandlerException("Can't instantiate Cache"); } mDatabaseEnabled = true; // Now do any global database (or other) initialization Log.d(TAG, "Initializing T2 database dispatcher"); Log.d(TAG, "Remote database name = " + mRemoteDatabase); mDispatchThread = new DispatchThread(); mDispatchThread.start(); } /** * Displays authentication dialog and takes the user through * the entire authentication process. * * @param thisActivity Calling party activity * */ public void logIn(final Activity thisActivity) { if (mAuthenticated) { new AlertDialog.Builder(mContext).setMessage("Already logged in, please logout first") .setPositiveButton("OK", null).setCancelable(true).create().show(); } else { if (mAllowTraditionalLogin) { GUIHelper.showEnterUserAndPassword(mContext, "", new LoginResult() { @Override public void result(boolean res, String username, String password) { Log.d(TAG, "username/password = " + username + " / " + password); if (res) { traditionalLogin(username, password); } else { // Causes Janrain to initiate login activity by showing login dialog // See callbacks jrAuthenticationDidReachTokenUrl() and jrAuthenticationDidSucceedForUser() // to see mAuthenticated getting set // mEngage.showAuthenticationDialog(thisActivity); } } }); } else { // Causes Janrain to initiate login activity by showing login dialog // See callbacks jrAuthenticationDidReachTokenUrl() and jrAuthenticationDidSucceedForUser() // to see mAuthenticated getting set // mEngage.showAuthenticationDialog(thisActivity); } } } /** * @deprecated use {@link #logIn(final Activity thisActivity)} * Displays authentication dialog and takes the user through * the entire authentication process. * * @param thisActivity Calling party activity */ public void showAuthenticationDialog(final Activity thisActivity) { if (mAuthenticated) { new AlertDialog.Builder(mContext).setMessage("Already logged in, please logout first") .setPositiveButton("OK", null).setCancelable(true).create().show(); } else { if (mAllowTraditionalLogin) { GUIHelper.showEnterUserAndPassword(mContext, "", new LoginResult() { @Override public void result(boolean res, String username, String password) { Log.d(TAG, "username/password = " + username + " / " + password); if (res) { traditionalLogin(username, password); } else { // Causes Janrain to initiate login activity by showing login dialog // See callbacks jrAuthenticationDidReachTokenUrl() and jrAuthenticationDidSucceedForUser() // to see mAuthenticated getting set // mEngage.showAuthenticationDialog(thisActivity); } } }); } else { // Causes Janrain to initiate login activity by showing login dialog // See callbacks jrAuthenticationDidReachTokenUrl() and jrAuthenticationDidSucceedForUser() // to see mAuthenticated getting set // mEngage.showAuthenticationDialog(thisActivity); } } } /** * Performs traditional login via Drupal services. * * @param username - username to use in Drupal login * @param password - password to use in Drupal login */ public void traditionalLogin(String username, String password) { UserServices us; us = new UserServices(mServicesClient); Log.d(TAG, "mServicesClient = " + mServicesClient); mProgressDialog = ProgressDialog.show(mContext, "", "Logging you in", true, false); us.Login(username, password, new AsyncHttpResponseHandler() { @Override public void onSuccess(String response) { Log.d(TAG, "response = " + response); mLoggedInAsTraditional = true; mAuthenticated = true; mProgressDialog.hide(); mProgressDialog.dismiss(); DrupalRegistrationResponse jsonResponse = new DrupalRegistrationResponse(response); if (jsonResponse != null) { mDrupalUserId = jsonResponse.mUid; } List<Cookie> list = new ArrayList<Cookie>(); list = mInstance.mCookieStore.getCookies(); Log.e(TAG, "CSRFSessionLookie = " + list.get(0).toString()); if (mRequiresCSRF) { if (VERBOSE_LOGGING) { Log.e(TAG, "Requesting CSRF Token"); } getCSRFToken(); } mAuthProvider = "Traditional"; mAuth_info = new JRDictionary(); if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthSuccess(mAuth_info, mAuthProvider, null, null); } } @Override public void onFailure(Throwable e, String response) { Log.e(TAG, e.toString()); mProgressDialog.hide(); mProgressDialog.dismiss(); mAuthProvider = "Traditional"; JREngageError error = new JREngageError(response, JREngageError.AuthenticationError.AUTHENTICATION_FAILED, JREngageError.ErrorType.AUTHENTICATION_FAILED); if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthFail(error, mAuthProvider); } } @Override public void onFinish() { Log.e(TAG, "traditionalLogin onFinish()"); mProgressDialog.hide(); mProgressDialog.dismiss(); } }); } /** * Performs traditional logout via Drupal Services */ void traditionalLogout() { UserServices us; us = new UserServices(mServicesClient); Log.d(TAG, "mServicesClient = " + mServicesClient); mServicesClient.setCookieStore(mCookieStore); mProgressDialog = ProgressDialog.show(mContext, "", "Logging you out", true, false); us.Logout(new AsyncHttpResponseHandler() { @Override public void onSuccess(String response) { Log.d(TAG, "response = " + response); mLoggedInAsTraditional = false; mAuthenticated = false; mProgressDialog.hide(); mProgressDialog.dismiss(); new AlertDialog.Builder(mContext).setMessage("Logout was successful.").setPositiveButton("OK", null) .setCancelable(true).create().show(); } @Override public void onFailure(Throwable e, String response) { Log.e(TAG, e.toString()); mProgressDialog.hide(); mProgressDialog.dismiss(); new AlertDialog.Builder(mContext).setMessage("Logout failed.").setPositiveButton("OK", null) .setCancelable(true).create().show(); } @Override public void onFinish() { mProgressDialog.hide(); mProgressDialog.dismiss(); } }); } /** * Cancels authentication */ public void logOut() { Log.d(TAG, "DataOuthandler Logging out"); if (mLoggedInAsTraditional) { traditionalLogout(); } mAuthenticated = false; drupalSessionCookie = null; // If we delete the cookie here, traditional logout will fail - so don't delete it! } /** * Enables logging to external log file of entries sent to the database * * @param context Calling party's context */ public void enableLogging(Context context) { try { mLogWriter = new LogWriter(context); String logFileName = mUserId + "_" + mSessionDate + ".log"; mLogWriter.open(logFileName, true); mLoggingEnabled = true; SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ", Locale.US); String timeId = sdf.format(new Date()); PackageManager packageManager = context.getPackageManager(); PackageInfo info = packageManager.getPackageInfo(context.getPackageName(), 0); mApplicationVersion = info.versionName; if (mLogFormat == GlobalH2.LOG_FORMAT_JSON) { String preamble = String.format( "{\"userId\" : \"%s\",\n" + "\"sessionDate\" : \"%s\",\n" + "\"timeId\" : \"%s\",\n" + "\"versionId\" : \"%s\",\n" + "\"data\":[", mUserId, mSessionDate, timeId, mApplicationVersion); mLogWriter.write(preamble); } } catch (Exception e) { Log.e(TAG, "Exception enabling logging: " + e.toString()); } } /** * Enables cat file logging of data puts */ public void enableLogCat() { mLogCatEnabled = true; } /** * Purges and closes the current log file. */ public void purgeLogFile() { if (mLoggingEnabled && mLogWriter != null) { if (mLogFormat == GlobalH2.LOG_FORMAT_JSON) { mLogWriter.write("],}"); } mLogWriter.close(); enableLogging(mContext); } } /** * Closes out any open log files and data connections */ public void close() { mServicesClient.mAsyncHttpClient.cancelRequests(mContext, true); Log.d(TAG, " ***********************************closing ******************************"); if (mLoggingEnabled && mLogWriter != null) { if (mLogFormat == GlobalH2.LOG_FORMAT_JSON) { mLogWriter.write("],}"); } mLogWriter.close(); } if (mDispatchThread != null) { mDispatchThread.cancel(); mDispatchThread.interrupt(); mDispatchThread = null; } // if (mEngage != null) { // mEngage.removeDelegate(this); // } mT2AuthDelegate = null; mAuthenticated = false; } /** * Handles creation of a new database entry * * @param dataOutPacket - packet to output to database * @throws DataOutHandlerException */ public void handleDataOut(final DataOutPacket dataOutPacket) throws DataOutHandlerException { if (mRequiresAuthentication == true && mAuthenticated == false) { throw new DataOutHandlerException("User is not authenticated"); } if (dataOutPacket == null) { throw new DataOutHandlerException("Data packet is null"); } if (mDatabaseEnabled) { dataOutPacket.mQueuedAction = "C"; Log.d(TAG, "Queueing document " + dataOutPacket.mRecordId); synchronized (mDbCache) { mDbCache.addPacketToCacheWithSendingStatus(dataOutPacket); } if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseCreateUpdateComplete(dataOutPacket); } } } /** * Updates a packet in the cache * * See handleDataOut() for the data path (Drupal) * * @param packet Packet to update * @throws DataOutHandlerException */ public void updateRecord(final DataOutPacket dataOutPacket) throws DataOutHandlerException { if (mRequiresAuthentication == true && mAuthenticated == false) { throw new DataOutHandlerException("User is not authenticated"); } SqlPacket sqlPacket = mDbCache.getPacketByDrupalId(dataOutPacket.mDrupalId); if (sqlPacket == null) { throw new DataOutHandlerException( "Packet DrupalID " + dataOutPacket.mDrupalId + " does not exist in Cache"); } else { Log.d(TAG, "Updating record " + dataOutPacket.mRecordId + ", " + dataOutPacket.mDrupalId); // Log.d(TAG, "Updating record " + sqlPacket.toString()); // Log.d(TAG, "From " + dataOutPacket.toString()); // We now have the original sql Packet corresponding to this dataOutPacket // We must transfer the changes from the dataOutPAcket to the sqlPAcket // before we do the update. SqlPacket sqlPacketNew = new SqlPacket(dataOutPacket); sqlPacketNew.setSqlPacketId(sqlPacket.getSqlPacketId()); int retVal = mDbCache.updateSqlPacket(sqlPacketNew); Log.d(TAG, "Updated: retVal = " + retVal + " drupalId = " + dataOutPacket.mDrupalId); SqlPacket updatedSqlPacketFromSql = mDbCache.getPacketByRecordId(dataOutPacket.mDrupalId); // The timed task will take care of updating it in Drupal if (mInstance.mDatabaseUpdateListener != null) { mInstance.mDatabaseUpdateListener.remoteDatabaseCreateUpdateComplete(dataOutPacket); } } } /** * Deletes data packet from the database * * @param dataOutPacket - packet to delete * @throws DataOutHandlerException */ public void deleteRecord(final DataOutPacket dataOutPacket) throws DataOutHandlerException { if (mRequiresAuthentication == true && mAuthenticated == false) { throw new DataOutHandlerException("User is not authenticated"); } if (mDatabaseEnabled) { SqlPacket sqlPacket = mDbCache.getPacketByRecordId(dataOutPacket.mRecordId); mNodeDeleteQueue.add(sqlPacket.getDrupalId()); // mNodeDeleteQueue.add(dataOutPacket.mDrupalId); mDbCache.deletePacketFromCacheWithDeletingStatus(dataOutPacket); // The timed task will take care of deleting it from Drupal if (mInstance.mDatabaseUpdateListener != null) { mInstance.mDatabaseUpdateListener.remoteDatabaseDeleteComplete(dataOutPacket); } } } /** * @author scott.coleman * * This thread handles maintenance of the cache * sending data out if the network is available. * */ class DispatchThread extends Thread { private boolean isRunning = false; private boolean cancelled = false; @Override public void run() { isRunning = true; while (true) { // Break out if this was cancelled. if (cancelled) { break; } for (int i = 1; i < 4; i++) { try { Thread.sleep(1000); if (cancelled) { break; } } catch (InterruptedException e) { e.printStackTrace(); } } //Log.d(TAG, "Http dispatch thread tick"); // If the network is available post entries from the PendingQueue if (isNetworkAvailable()) { if (mRequiresAuthentication) { if (mAuthenticated == true) { ProcessCacheThread(); } } else { ProcessCacheThread(); } } // End if (isNetworkAvailable()) } // End while(true) isRunning = false; } // End public void run() /** * Cancel the loop */ public void cancel() { this.cancelled = true; Log.e(TAG, "Cancelled"); } /** * * @return true if running false otherwise */ public boolean isRunning() { return this.isRunning; } } // End DispatchThread /** * @return true if network is available */ private boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); // if no network is available networkInfo will be null // otherwise check if we are connected if (networkInfo != null && networkInfo.isConnected()) { return true; } return false; } /** * Logs a text note to sinks * * @param note - Text not to log to sinks * @throws DataOutHandlerException */ public void logNote(String note) throws DataOutHandlerException { DataOutPacket packet = new DataOutPacket(); packet.add(DataOutHandlerTags.NOTE, note); handleDataOut(packet); } /* (non-Javadoc) * Callback that indicates a successful JanRain authentication. * * @see com.janrain.android.engage.JREngageDelegate#jrAuthenticationDidSucceedForUser(com.janrain.android.engage.types.JRDictionary, java.lang.String) */ @Override public void jrAuthenticationDidSucceedForUser(JRDictionary auth_info, String provider) { Log.d(TAG, "jrAuthenticationDidSucceedForUser"); mAuth_info = auth_info; // Note, if we're using drupal the authentication isn't // really done until the callback URL has been called // This sets up the Drupal Database if (mDatabaseType == DATABASE_TYPE_T2_DRUPAL) { } else { mAuthProvider = provider; mAuthenticated = true; if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthSuccess(mAuth_info, mAuthProvider, null, null); } } } /* (non-Javadoc) * Callback that indicates Janrain successfully contacted the TokenURL server (after authentication) * * @see com.janrain.android.engage.JREngageDelegate#jrAuthenticationDidReachTokenUrl(java.lang.String, com.janrain.android.engage.net.async.HttpResponseHeaders, java.lang.String, java.lang.String) */ @Override public void jrAuthenticationDidReachTokenUrl(String tokenUrl, HttpResponseHeaders responseHeaders, String responsePayload, String provider) { Log.d(TAG, "jrAuthenticationDidReachTokenUrl"); // Check to see of Janrain is supplying a drupal session cookie org.apache.http.cookie.Cookie[] mSessionCookies; mSessionCookies = responseHeaders.getCookies(); for (Cookie cookie : mSessionCookies) { System.out.println("Cookie! - " + cookie.toString()); System.out.println("Cokie Name - " + cookie.getName()); if (cookie.getName().startsWith("SESS") || cookie.getName().startsWith("SSESS")) { drupalSessionCookie = cookie; if (VERBOSE_LOGGING) Log.e(TAG, "saving session cookie: " + cookie.toString()); } } if (mRequiresCSRF) { if (VERBOSE_LOGGING) { Log.e(TAG, "Requesting CSRF Token"); } getCSRFToken(); } mAuthenticated = true; if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthSuccess(mAuth_info, mAuthProvider, responseHeaders, responsePayload); } } @Override public void jrSocialDidPublishJRActivity(JRActivityObject activity, String provider) { } @Override public void jrSocialDidCompletePublishing() { } @Override public void jrEngageDialogDidFailToShowWithError(JREngageError error) { Log.d(TAG, "jrEngageDialogDidFailToShowWithError"); } @Override public void jrAuthenticationDidNotComplete() { Log.d(TAG, "jrAuthenticationDidNotComplete"); } /* (non-Javadoc) * Callback that indicates JanRain encountered a failure authenticating. * * @see com.janrain.android.engage.JREngageDelegate#jrAuthenticationDidFailWithError(com.janrain.android.engage.JREngageError, java.lang.String) */ @Override public void jrAuthenticationDidFailWithError(JREngageError error, String provider) { Log.d(TAG, "jrAuthenticationDidFailWithError"); mAuthenticated = false; if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthFail(error, provider); } } /* (non-Javadoc) * Callback that indicates JanRain encountered a failure contacting the token URL * * @see com.janrain.android.engage.JREngageDelegate#jrAuthenticationCallToTokenUrlDidFail(java.lang.String, com.janrain.android.engage.JREngageError, java.lang.String) */ @Override public void jrAuthenticationCallToTokenUrlDidFail(String tokenUrl, JREngageError error, String provider) { Log.d(TAG, "jrAuthenticationCallToTokenUrlDidFail"); mAuthenticated = false; if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthFail(error, provider); } } @Override public void jrSocialDidNotCompletePublishing() { if (mT2AuthDelegate != null) { mT2AuthDelegate.T2AuthNotCompleted(); } } @Override public void jrSocialPublishJRActivityDidFail(JRActivityObject activity, JREngageError error, String provider) { } /** * Processes the cache (updates the local cache and remote database to they are in sync) * To be called from DispatchThread * * Here is the algorithm: * 1. Request a packet summary from Drupal * 2. Check for records in local cache but not in remote DB * if Packet exists in Cache but not in remote DB * if Packet newly inserted by self * Add (send) the packet to the remote DB * else (Packet deleted by other from remote DB) * Remove the packet from the cache * 2. Check for records in remote DB but not in local cache * if Packet exists in remote DB but not in local Cache * if Packeted deleted by self * Delete packet from remote DB * else (Packet newly inserted by other into DB) * Add packet to local Cache * 3. If records exist both in local cache and remote DB * if the remote DB record changed data is more recent * Update the local cache from the remote DB * else * Update the remote DB from the local cache * * * Rules for presence of drupal node id in the cache * If synched from DB (added by another client): * drupal ID is present immediately * If added from the UI * drupal id is blank until the 200 ok is received from the Drupal server */ private void ProcessCacheThread() { final ArrayList<String> mDrupalNodeIdList = new ArrayList<String>(); final ArrayList<String> mSqlNodeIdList; final HashMap<String, String> mDrupalRecordIdToDrupalChangedTime = new HashMap<String, String>(); Log.d(TAG, "ProcessCacheThread()"); // Get list of all records in the local database. This will be comared // to the list of records in the drupal database // in order to determine what needs to be synched mSqlNodeIdList = (ArrayList<String>) mDbCache.getSqlNodeIdList(); // ------------------------------------- // Get Drupal Node Summary: // ------------------------------------- UserServices us; us = new UserServices(mServicesClient); JsonHttpResponseHandler responseHandler = new JsonHttpResponseHandler() { @Override protected void handleSuccessJsonMessage(Object arg0) { // if (VERBOSE_LOGGING) { // Log.e(TAG, "handleSuccessJsonMessage(Object arg0"); //// Log.e(TAG, "Drupal Node Summary: " + array.toString()); // } JSONArray array = (JSONArray) arg0; for (int i = 0; i < array.length(); i++) { JSONObject jObject = (JSONObject) array.opt(i); try { String userId = (String) jObject.get("uid"); String nodeId = (String) jObject.get("nid"); String changedTime = (String) jObject.get("changed"); String type = (String) jObject.get("type"); // Check to see if this is a valid record // If so then add the record to the summary arrays if (GlobalH2.isValidRecordType(type)) { mDrupalNodeIdList.add(nodeId); mDrupalRecordIdToDrupalChangedTime.put(nodeId, changedTime); // if (VERBOSE_LOGGING) { // Log.e(TAG, "setting Array nodeId/changed " + nodeId + " = " + changedTime); // } } } catch (JSONException e) { e.printStackTrace(); } } if (VERBOSE_LOGGING) { Log.e(TAG, "mDrupalNodeIdList = " + mDrupalNodeIdList.toString()); Log.e(TAG, "mSqlNodeIdList = " + mSqlNodeIdList.toString()); } } @Override public void onSuccess(JSONObject response) { if (VERBOSE_LOGGING) { Log.e(TAG, "onSuccess(JSONObject response) "); } } @Override public void onSuccess(JSONArray arg0) { // if (VERBOSE_LOGGING) { // Log.e(TAG, "onSuccess(JSONArray arg0) "); // } super.onSuccess(arg0); } @Override public void onFailure(Throwable e, JSONObject response) { Log.e(TAG, "OnFailure(Throwable e, JSONObject response) " + e.toString()); } @Override public void onFailure(Throwable arg0, JSONArray arg1) { Log.e(TAG, "OnFailure(Throwable arg0, JSONArray arg1) " + arg0.toString()); } /* (non-Javadoc) * The HTTP call to retrieve the summary contents from Drupal has succeeded. * Now process the results. * * @see com.loopj.android.http.AsyncHttpResponseHandler#onFinish() */ @Override public void onFinish() { Log.d(TAG, "onFinish(Drupal Node Summary)"); // Now do processing of cache items, comparing what's on the device // to what's in the remote database. if (true) { // Now do record by record comparison between the records of the local database (mSqlRecordIdList) // to the records of the remote database (mDrupalNodeIdList). // Check for records in local cache but not in remote DB for (String sqlNodeId : mSqlNodeIdList) { if (!mDrupalNodeIdList.contains(sqlNodeId)) { if (VERBOSE_LOGGING) { Log.e(TAG, "sqlNodeId: " + sqlNodeId + " - Packet exists in local Cache but not in remote DB"); } if (sqlNodeId == null) { break; } // Initially the nodeId is equal to the RecordId we can use getPacketByRecordId here // Thereafter we can't because nodeId will have been replaced from the Drupal Server SqlPacket sqlPacket = mDbCache.getPacketByDrupalId(sqlNodeId); // Since nodeId is in mSqlNodeIdList we know this will not return null // Exists in cache but not on on Drupal // Two possible cases here // 1 Packet newly inserted by self -> Add (send) the packet to the remote DB // 2 Packet deleted by other from remote DB -> Remove the packet from the local cache Boolean isSendingOrSend = (sqlPacket.getCacheStatus() == GlobalH2.CACHE_SENDING || sqlPacket.getCacheStatus() == GlobalH2.CACHE_SENT); if (isSendingOrSend) { // Packet exists in cache but not on on Drupal // Case 1 - Packet newly inserted by self -> Add (send) the packet to the DB if (VERBOSE_LOGGING) { Log.e(TAG, "Case 1 - Send the packet to remoteDB"); } DataOutPacket dataOutPacket; if (VERBOSE_LOGGING) { Log.e(TAG, "Status Sending/Sent - Sending packet to remote database "); } try { synchronized (mDbCache) { sqlPacket.setCacheStatus(GlobalH2.CACHE_SENT); mDbCache.updateSqlPacket(sqlPacket); } dataOutPacket = new DataOutPacket(sqlPacket); sendPacketToRemoteDbSync(dataOutPacket, "C", ""); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } else { // Packet exists in cache but not on on Drupal // Case 2 - Packet deleted by other from remote DB -> Remove the packet from the local cache if (VERBOSE_LOGGING) { Log.e(TAG, "Case 2 - Remove the packet from the local cache"); } mDbCache.deletePacketFromCache(sqlPacket); DataOutPacket dataOutPacket; try { // TODO: calling this too often slows the UI dataOutPacket = new DataOutPacket(sqlPacket); if (mInstance.mDatabaseUpdateListener != null) { mInstance.mDatabaseUpdateListener .remoteDatabaseDeleteComplete(dataOutPacket); } } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } } // if (!mDrupalNodeIdList.contains(id)) else { // Record exists in both local cache and remote db // Need to merge the records // if (VERBOSE_LOGGING) { // Log.e(TAG, "Record exists in both local cache and remote db: " + sqlNodeId); // } // First see which record is most recent try { SqlPacket sqlPacket = mDbCache.getPacketByDrupalId(sqlNodeId); if (sqlPacket != null) { String localChangedAt = sqlPacket.getChangedDate(); //DateTime dtLocalChangedAt = new DateTime(localChangedAt); //long lLocalChangedAt = dtLocalChangedAt.getMillis(); long lLocalChangedAt = Long.parseLong(localChangedAt); String drupalChangedAt = mDrupalRecordIdToDrupalChangedTime.get(sqlNodeId); // long lDrupalChangedAt = Long.parseLong(drupalChangedAt) * 1000; long lDrupalChangedAt = Long.parseLong(drupalChangedAt); if (VERBOSE_LOGGING) { // Log.e(TAG, "Record exists in both local and remote, times: " + lLocalChangedAt + ", " + lDrupalChangedAt + ", " + sqlNodeId); if (lDrupalChangedAt == lLocalChangedAt) { //Log.e(TAG, "RemoteTime and local time are exactly equal"); } else { if (lDrupalChangedAt > lLocalChangedAt) { Log.e(TAG, "RemoteTime is most recent - updating cache from remote"); addPacketToCacheSync(sqlNodeId, "U"); // Grabs the packet from Drupal and updates it in the Cache } else { Log.e(TAG, "localTime is most recent"); if (VERBOSE_LOGGING) { Log.e(TAG, "Updatating record to remote database"); } DataOutPacket dataOutPacket = new DataOutPacket(sqlPacket); sendPacketToRemoteDbSync(dataOutPacket, "U", sqlNodeId); } } } } } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } // else } // end for (String id : mSqlIdList) // // Check for records in remote DB but not in local cache for (String drupalNodeId : mDrupalNodeIdList) { if (!mSqlNodeIdList.contains(drupalNodeId)) { if (VERBOSE_LOGGING) { Log.e(TAG, "drupalNodeId: " + drupalNodeId + " - Packet exists in remote DB but not in local Cache"); } // Packet exists in remote DB but not in local Cache // Two possible cases here: // 3 Packeted deleted by self -> Delete packet from remoteDB // 4 Packet newly inserted by other into remote DB -> Add packet to local Cache //Log.e(TAG, "mNodeDeleteQueue = " + mNodeDeleteQueue.toString()); boolean listContainsId = false; for (String id : mNodeDeleteQueue) { if (id != null && id.equalsIgnoreCase(drupalNodeId)) { listContainsId = true; break; } } // Determine which of the cases we have here if (listContainsId) { // Packet exists in remote DB but not in local Cache // Case 3 - Packeted deleted by self -> Delete packet from remote DB if (VERBOSE_LOGGING) { Log.e(TAG, "Case 3 - Delete packet from remote DB"); } sendPacketToRemoteDbSync(null, "D", drupalNodeId); } else { // Packet exists in DB but not in Cache // Case 4 - Packet newly inserted by other into DB -> Add packet to Cache if (VERBOSE_LOGGING) { Log.e(TAG, "Case 4 - Add packet to local cache"); } addPacketToCacheSync(drupalNodeId, "C"); // Grabs the packet from Drupal and adds it to the Cache } } } } try { // if (VERBOSE_LOGGING) { // Log.e(TAG, "Done processing all Drupal check entries"); // } // Notify self that all entries have been processed synchronized (updateCacheSyncToken) { updateCacheSyncToken.notifyAll(); } } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } // void onFinish() }; // if (VERBOSE_LOGGING) { // Log.e(TAG, "Requesting drupal node summary"); // } if (mDrupalUserId.equalsIgnoreCase("")) { us.NodeGet(responseHandler); } else { if (mFilterQueriesOnUserId) { us.NodeGet(responseHandler, "?parameters[uid]=" + mDrupalUserId); } else { us.NodeGet(responseHandler, ""); } } if (VERBOSE_LOGGING) { Log.e(TAG, "Waiting for UpdateCacheSyncToken"); } // Wait until all entries have been processed synchronized (updateCacheSyncToken) { try { updateCacheSyncToken.wait(SYNC_TIMEOUT); } catch (InterruptedException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } if (VERBOSE_LOGGING) { Log.e(TAG, "Done Waiting for UpdateCacheSyncToken"); } // Give notice that the internal cache should now be in sync with the remote database if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseSyncComplete(); } } /** * Requests a CSRF token from the Drupal server * This token is then send to the Services client to include as a header * Note that this is only required in newer Drupal Services installations */ void getCSRFToken() { UserServices us; int nodeNum = 0; // Check to see if we've stored a Drupal session cookie. If so then attach then to // the http client. // Note: if logged in as traditional there will be no explicit store of the session cookie // (It's done automagically) so skip this if (drupalSessionCookie != null) { mCookieStore.addCookie(drupalSessionCookie); mServicesClient.setCookieStore(mCookieStore); if (VERBOSE_LOGGING) { Log.e(TAG, "Using session cookie: " + drupalSessionCookie.toString()); } } us = new UserServices(mServicesClient); AsyncHttpResponseHandler responseHandler = new AsyncHttpResponseHandler() { @Override public void onSuccess(String arg0) { super.onSuccess(arg0); Log.e(TAG, "getCSRFToken() SUCCESS: " + arg0); mCSRFToken = arg0; mServicesClient.setCSRFToken(arg0); } @Override public void onFailure(Throwable e, String response) { Log.e(TAG, "getCSRFToken() FAILED: " + response); Log.e(TAG, e.toString()); } @Override public void onFinish() { Log.e(TAG, "getCSRFToken() OnFinish()"); } }; us.RequestCSRFToken(responseHandler); } /** * This extends the normal JsonHttpResponseHandler with the difference * being that this keeps track of the specific DataOutPacket that was sent * so it can be updated when the response comes * * @author scott.coleman * */ public class T2AsyncHttpResponseHandler extends JsonHttpResponseHandler { private DataOutPacket mDataoutPacket; public T2AsyncHttpResponseHandler(DataOutPacket dataoutPacket) { mDataoutPacket = dataoutPacket; } @Override public void onSuccess(String arg0) { super.onSuccess(arg0); } @Override public void onFailure(Throwable arg0, JSONArray arg1) { Log.e(TAG, arg0.toString()); if (mDataoutPacket.mRecordId != null) { mDbCache.deletePacketFromCacheWithDeletingStatus(mDataoutPacket); } if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseFailure(arg0.toString()); } super.onFailure(arg0, arg1); } @Override public void onFailure(Throwable e, JSONObject response) { Log.e(TAG, e.toString() + response.toString()); //need to set flag so this gets set to idle if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseFailure(e.toString()); } super.onFailure(e, response); } @Override public void onSuccess(JSONArray arg0) { Log.d(TAG, "Successfully submitted ARRAY (Deleted record from drupal)" + arg0.toString()); updatemNodeDeleteQueue(); super.onSuccess(arg0); } @Override public void onSuccess(JSONObject arg0) { String drupalNodeId; try { drupalNodeId = arg0.getString("nid"); Log.d(TAG, "T2AsyncHttpResponseHandler - Successfully submitted article # " + drupalNodeId); // Set node id for record if (mDataoutPacket.mRecordId != null) { SqlPacket sqlPacket = mDbCache.getPacketByRecordId(mDataoutPacket.mRecordId); // if (VERBOSE_LOGGING) { // Log.e(TAG, "setting DrupalNodeId " + mDataoutPacket.mRecordId + ", " + drupalNodeId + " to idle"); // } for (DBObject object : mDBObjects) { if (object.mRecordId.equalsIgnoreCase(mDataoutPacket.mRecordId)) { object.mDrupalId = drupalNodeId; } } //sqlPacket.setRecordId(drupalNodeId); sqlPacket.setDrupalId(drupalNodeId); mDataoutPacket.mDrupalId = drupalNodeId; // if (VERBOSE_LOGGING) { // Log.e(TAG, "RecordId " + mDataoutPacket.mRecordId + " now has DrupalId " + mDataoutPacket.mDrupalId); // } // Now set the status of the cache packet to idle sqlPacket.setCacheStatus(GlobalH2.CACHE_IDLE); mDbCache.updateSqlPacket(sqlPacket); if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseCreateUpdateComplete(mDataoutPacket); } } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } super.onSuccess(arg0); } } /** * Synchronous version of sendPacketToRemoteDbSync. * Doesn't return until HTTP transaction is either complete or has timed out. * @param dataOutPacket * @param queuedAction * @param drupalNodeId */ void sendPacketToRemoteDbSync(final DataOutPacket dataOutPacket, final String queuedAction, final String drupalNodeId) { if (VERBOSE_LOGGING) { Log.e(TAG, "Waiting for sendPacketToRemoteDbToken"); } synchronized (sendPacketToRemoteDbToken) { sendPacketToRemoteDb(dataOutPacket, queuedAction, drupalNodeId); try { sendPacketToRemoteDbToken.wait(SYNC_TIMEOUT); } catch (InterruptedException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } if (VERBOSE_LOGGING) { Log.e(TAG, "Done Waiting for sendPacketToRemoteDbToken"); } } /** * Sends a dataOutPAcket Drupal database for processing * * @param jsonString */ void sendPacketToRemoteDb(final DataOutPacket dataOutPacket, final String queuedAction, final String drupalNodeIdToDelete) { UserServices us; String jsonString = ""; if (dataOutPacket != null) { Log.d(TAG, "sendPacketToRemoteDb(" + dataOutPacket.mRecordId + ")"); if (dataOutPacket.mStructureType.equalsIgnoreCase(DataOutHandlerTags.STRUCTURE_TYPE_CHECKIN)) { Checkin checkin = new Checkin(dataOutPacket); jsonString = checkin.drupalize(); } else if (dataOutPacket.mStructureType.equalsIgnoreCase(DataOutHandlerTags.STRUCTURE_TYPE_HABIT)) { Habit habit = new Habit(dataOutPacket); jsonString = habit.drupalize(); } else { jsonString = dataOutPacket.drupalize(); } // jsonString = fred; } else { Log.d(TAG, "sendPacketToRemoteDb - deleting drupal node id + " + drupalNodeIdToDelete + ")"); } Log.e(TAG, "Sending JsonString = " + jsonString); // Check to see if we've stored a Drupal session cookie. If so then attach then to // the http client. // Note: if logged in as traditional there will be no explicit store of the session cookie // (It's done automagically) so skip this if (drupalSessionCookie != null) { mCookieStore.addCookie(drupalSessionCookie); mServicesClient.setCookieStore(mCookieStore); if (VERBOSE_LOGGING) { Log.e(TAG, "Using session cookie: " + drupalSessionCookie.toString()); } } else { if (!mLoggedInAsTraditional) { // For traditional login the cookies are implicit Log.e(TAG, "No Stored Cookies to use: "); } } us = new UserServices(mServicesClient); T2AsyncHttpResponseHandler responseHandler = new T2AsyncHttpResponseHandler(dataOutPacket) { // Note: callbacks for this are in T2AsyncHttpResponseHandler @Override public void onFinish() { if (queuedAction.equalsIgnoreCase("D")) { Log.d(TAG, "onFinish(" + drupalNodeIdToDelete + ")"); } else { if (dataOutPacket != null) Log.d(TAG, "onFinish(" + dataOutPacket.mRecordId + ")"); } synchronized (sendPacketToRemoteDbToken) { Log.d(TAG, "onFinish(addPacketToRemoteDrupalPacketCache)"); sendPacketToRemoteDbToken.notify(); } } }; if (queuedAction.equalsIgnoreCase("C")) { us.NodePost(jsonString, responseHandler); } else if (queuedAction.equalsIgnoreCase("U")) { us.NodePut(jsonString, responseHandler, drupalNodeIdToDelete); } else if (queuedAction.equalsIgnoreCase("D")) { us.NodeDelete(responseHandler, drupalNodeIdToDelete); } } /** * Synchronous version of addPacketToCache. * Doesn't return until HTTP transaction is either complete or has timed out. * * @param drupalNodeId */ void addPacketToCacheSync(final String drupalNodeId, String action) { if (VERBOSE_LOGGING) { Log.e(TAG, "Waiting for addPacketToCacheSyncToken"); } addPacketToCache(drupalNodeId, action); synchronized (addPacketToCacheSyncToken) { try { addPacketToCacheSyncToken.wait(SYNC_TIMEOUT); } catch (InterruptedException e) { e.printStackTrace(); } } if (VERBOSE_LOGGING) { Log.e(TAG, "Done Waiting for addPacketToCacheSyncToken"); } } /** * Retrieves the contents of the updated packet from Drupal and updates the Cache * * @param drupalId record id of drupal packet added */ void addPacketToCache(final String drupalId, final String action) { UserServices us; int nodeNum = 0; Log.d(TAG, "addPacketToCache(" + drupalId + "), action = " + action); us = new UserServices(mServicesClient); JsonHttpResponseHandler responseHandler = new JsonHttpResponseHandler() { @Override public void onSuccess(JSONObject response) { try { String title = (String) response.get("title"); Log.d(TAG, "Got object (" + title + "), now adding to cache, drupalId = " + drupalId); // Log.d(TAG, "response = " + response); } catch (JSONException e1) { Log.e(TAG, e1.toString()); e1.printStackTrace(); } String drupalNodeContents = response.toString(); // Now convert the drupal node to a dataOutPacket. DataOutPacket dataOutPacket; try { dataOutPacket = new DataOutPacket(response); // Log.d(TAG, "dataOutPacket = " + dataOutPacket.toString()); if (action.equalsIgnoreCase("C")) { // Make sure to set the drupal id while adding it to the cache synchronized (mDbCache) { mDbCache.addPacketToCache(dataOutPacket, dataOutPacket.mRecordId); } } else { updateRecord(dataOutPacket); } // TODO: calling this too often slows down the UI if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseCreateUpdateComplete(dataOutPacket); } } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); //e.printStackTrace(); } } @Override public void onSuccess(JSONArray arg0) { super.onSuccess(arg0); } @Override public void onFailure(Throwable e, JSONObject response) { Log.e(TAG, e.toString()); if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseFailure(e.toString()); } } @Override public void onFailure(Throwable arg0, JSONArray arg1) { Log.e(TAG, arg0.toString()); if (mDatabaseUpdateListener != null) { mDatabaseUpdateListener.remoteDatabaseFailure(arg0.toString()); } } @Override public void onFinish() { synchronized (addPacketToCacheSyncToken) { if (VERBOSE_LOGGING) { Log.d(TAG, "onFinish(addPacketToCache)"); } addPacketToCacheSyncToken.notify(); } } }; try { nodeNum = Integer.parseInt(drupalId); us.NodeGet(nodeNum, responseHandler); // Log.e(TAG, "Waiting for addPacketToCacheSyncToken"); // synchronized (addPacketToCacheSyncToken) // { // try { // addPacketToCacheSyncToken.wait(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } // Log.e(TAG, "Done Waiting for addPacketToCacheSyncToken"); } catch (NumberFormatException e1) { Log.e(TAG, e1.toString()); } } void updatemNodeDeleteQueue() { } /** * Requests a list of DataOutPackets from the local cache * This version returns all data types */ public ArrayList<DataOutPacket> getPacketList() { return mDbCache.getPacketList(); } /** * Requests a list of DataOutPackets from the local cache * The list returned is filtered by the data types contained in * the list structureTypes. Note that you must use only * pre-configured data types. ie sensor_data, habit, checkin, etc. * @param structureTypes - List of Structure types to filter on * @return - List of DataOutPackets in the local cache */ public ArrayList<DataOutPacket> getPacketList(List<String> structureTypes) { return mDbCache.getPacketList(structureTypes); } /** * Requests a list of DataOutPackets from the local cache * @param whereClause - where clause to use in SQL statement * @return - List of DataOutPackets in the local cache */ public ArrayList<DataOutPacket> getPacketList(String whereClause) { return mDbCache.getPacketList(whereClause); } public ArrayList<DataOutPacket> getCheckinsForHabit(Habit habit) { // First get all checkins ArrayList<DataOutPacket> list = mDbCache .getPacketList("StructureType in ('" + DataOutHandlerTags.STRUCTURE_TYPE_CHECKIN + "')"); // Now pick out only checks for given habit ArrayList<DataOutPacket> checkinList = new ArrayList<DataOutPacket>(); // for (DataOutPacket packet : list) { // if (packet.) { // // } // } return checkinList; } }