Java tutorial
/* * Copyright 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.network.sync.basicsyncadapter; import android.accounts.Account; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.Context; import android.content.OperationApplicationException; import android.content.SyncResult; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; import com.example.android.network.sync.basicsyncadapter.models.Boiler; import com.example.android.network.sync.basicsyncadapter.models.Pipe; import com.example.android.network.sync.basicsyncadapter.models.Transformer; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.xmlpull.v1.XmlPullParserException; import org.json.*; import com.example.android.network.sync.basicsyncadapter.models.Turbine; import com.google.gson.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Define a sync adapter for the app. * * <p>This class is instantiated in {@link SyncService}, which also binds SyncAdapter to the system. * SyncAdapter should only be initialized in SyncService, never anywhere else. * * <p>The system calls onPerformSync() via an RPC call through the IBinder object supplied by * SyncService. */ class SyncAdapter extends AbstractThreadedSyncAdapter { public static final String TAG = "SyncAdapter"; public ArrayList<Class> modelsRegisteredForSync; /** * URL to fetch content from during a sync. * * <p>This points to the Android Developers Blog. (Side note: We highly recommend reading the * Android Developer Blog to stay up to date on the latest Android platform developments!) */ private static final String FEED_URL = "https://api.parse.com/1/classes/Transformer"; /** * Network connection timeout, in milliseconds. */ private static final int NET_CONNECT_TIMEOUT_MILLIS = 15000; // 15 seconds /** * Network read timeout, in milliseconds. */ private static final int NET_READ_TIMEOUT_MILLIS = 10000; // 10 seconds /** * Content resolver, for performing database operations. */ private final ContentResolver mContentResolver; // Constants representing column positions from PROJECTION. public static final int COLUMN_ID = 0; public static final int COLUMN_ENTRY_ID = 1; public static final int COLUMN_TITLE = 2; public static final int COLUMN_LINK = 3; public static final int COLUMN_PUBLISHED = 4; /** * Constructor. Obtains handle to content resolver for later use. */ public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); mContentResolver = context.getContentResolver(); addModelsToSync(); } /** * Constructor. Obtains handle to content resolver for later use. */ public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); mContentResolver = context.getContentResolver(); addModelsToSync(); } private void addModelsToSync() { modelsRegisteredForSync = new ArrayList<Class>(); modelsRegisteredForSync.add(Transformer.class); modelsRegisteredForSync.add(Boiler.class); modelsRegisteredForSync.add(Turbine.class); modelsRegisteredForSync.add(Pipe.class); } /** * Called by the Android system in response to a request to run the sync adapter. The work * required to read data from the network, parse it, and store it in the content provider is * done here. Extending AbstractThreadedSyncAdapter ensures that all methods within SyncAdapter * run on a background thread. For this reason, blocking I/O and other long-running tasks can be * run <em>in situ</em>, and you don't have to set up a separate thread for them. . * * <p>This is where we actually perform any work required to perform a sync. * {@link AbstractThreadedSyncAdapter} guarantees that this will be called on a non-UI thread, * so it is safe to peform blocking I/O here. * * <p>The syncResult argument allows you to pass information back to the method that triggered * the sync. */ @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { Log.i(TAG, "Beginning network synchronization"); try { final URL location = new URL(FEED_URL); InputStream stream = null; try { Log.i(TAG, "Streaming data from network: " + location); downloadAllObjectsForRegisteredModels(syncResult); //stream = downloadUrl(location); //updateLocalFeedData(stream, syncResult); // Makes sure that the InputStream is closed after the app is // finished using it. } finally { if (stream != null) { stream.close(); } } } catch (InvocationTargetException e) { } catch (IllegalAccessException e) { } catch (MalformedURLException e) { Log.wtf(TAG, "Feed URL is malformed", e); syncResult.stats.numParseExceptions++; return; } catch (IOException e) { Log.e(TAG, "Error reading from network: " + e.toString()); syncResult.stats.numIoExceptions++; return; } /*catch (XmlPullParserException e) { Log.e(TAG, "Error parsing feed: " + e.toString()); syncResult.stats.numParseExceptions++; return; } catch (ParseException e) { Log.e(TAG, "Error parsing feed: " + e.toString()); syncResult.stats.numParseExceptions++; return; } catch (RemoteException e) { Log.e(TAG, "Error updating database: " + e.toString()); syncResult.databaseError = true; return; } catch (OperationApplicationException e) { Log.e(TAG, "Error updating database: " + e.toString()); syncResult.databaseError = true; return; }*/ Log.i(TAG, "Network synchronization complete"); } /** * Read XML from an input stream, storing it into the content provider. * * <p>This is where incoming data is persisted, committing the results of a sync. In order to * minimize (expensive) disk operations, we compare incoming data with what's already in our * database, and compute a merge. Only changes (insert/update/delete) will result in a database * write. * * <p>As an additional optimization, we use a batch operation to perform all database writes at * once. * * <p>Merge strategy: * 1. Get cursor to all items in feed<br/> * 2. For each item, check if it's in the incoming data.<br/> * a. YES: Remove from "incoming" list. Check if data has mutated, if so, perform * database UPDATE.<br/> * b. NO: Schedule DELETE from database.<br/> * (At this point, incoming database only contains missing items.)<br/> * 3. For any items remaining in incoming list, ADD to database. */ public void updateLocalFeedData(final InputStream stream, final SyncResult syncResult) throws IOException, XmlPullParserException, RemoteException, OperationApplicationException, ParseException { final ContentResolver contentResolver = getContext().getContentResolver(); Log.i(TAG, "Parsing stream as Atom feed"); final List<Transformer> entries = null; /*=this.parseTransformersResponse(stream)*/; ; Log.i(TAG, "Parsing complete. Found " + entries.size() + " entries"); ArrayList<ContentProviderOperation> batch = new ArrayList<ContentProviderOperation>(); // Build hash table of incoming entries HashMap<String, Transformer> entryMap = new HashMap<String, Transformer>(); for (Transformer e : entries) { entryMap.put(e.transformerID, e); } Cursor c = null; try { // Get list of all items Log.i(TAG, "Fetching local entries for merge"); Uri uri = Transformer.CONTENT_URI; // Get all entries c = contentResolver.query(uri, null, null, null, null); assert c != null; Log.i(TAG, "Found " + c.getCount() + " local entries. Computing merge solution..."); } catch (Exception ex) { } // Find stale data String id; String name; String location; while (c.moveToNext()) { syncResult.stats.numEntries++; id = c.getColumnName(COLUMN_ID); name = c.getString(COLUMN_ENTRY_ID); location = c.getString(COLUMN_TITLE); Transformer match = entryMap.get(id); if (match != null) { // Entry exists. Remove from entry map to prevent insert later. entryMap.remove(id); // Check to see if the entry needs to be updated Uri existingUri = Transformer.CONTENT_URI.buildUpon().appendPath(id).build(); if ((match.trsName != null && !match.trsLocation.equals(name))) { // Update existing record Log.i(TAG, "Scheduling update: " + existingUri); batch.add(ContentProviderOperation.newUpdate(existingUri).withValue(Transformer.KEY_NAME, name) .withValue(Transformer.KEY_LOCATION, location) .build()); syncResult.stats.numUpdates++; } else { Log.i(TAG, "No action: " + existingUri); } } else { // Entry doesn't exist. Remove it from the database. Uri deleteUri = Transformer.CONTENT_URI.buildUpon().appendPath(id).build(); Log.i(TAG, "Scheduling delete: " + deleteUri); batch.add(ContentProviderOperation.newDelete(deleteUri).build()); syncResult.stats.numDeletes++; } } c.close(); // Add new items for (Transformer e : entryMap.values()) { Log.i(TAG, "Scheduling insert: entry_id=" + e.transformerID); batch.add(ContentProviderOperation.newInsert(Transformer.CONTENT_URI) .withValue(Transformer.KEY_TRANSFORMER_ID, e.transformerID) .withValue(Transformer.KEY_NAME, e.trsName).withValue(Transformer.KEY_LOCATION, e.trsLocation) .withValue(Transformer.KEY_CURRENT_TEMP, e.trsCurrentTemp) .withValue(Transformer.KEY_LAST_SERVER_SYNC_DATE, e.lastServerSyncDate) .withValue(Transformer.KEY_LAST_UPDATED_TIME, e.lastServerSyncDate) .withValue(Transformer.KEY_SYNC_STATUS, 0).withValue(Transformer.KEY_MAKE, e.trsMake) .withValue(Transformer.KEY_WINDING_MAKE, e.trsWindingMake) .withValue(Transformer.KEY_WINDING_COUNT, e.trsWindingCount) .withValue(Transformer.KEY_OIL_LEVEL, e.trsOilLevel) .withValue(Transformer.KEY_OPERATING_POWER, e.trsOperatingPower) .withValue(Transformer.KEY_TYPE, e.trsType) .build()); syncResult.stats.numInserts++; } Log.i(TAG, "Merge solution ready. Applying batch update"); mContentResolver.applyBatch(Transformer.CONTENT_AUTHORITY, batch); mContentResolver.notifyChange(Transformer.CONTENT_URI, // URI where data was modified null, // No local observer false); // IMPORTANT: Do not sync to network // This sample doesn't support uploads, but if *your* code does, make sure you set // syncToNetwork=false in the line above to prevent duplicate syncs. } private void downloadAllObjectsForRegisteredModels(SyncResult syncResults) throws InvocationTargetException, IllegalAccessException { //first see if there are any rows with sync status with 0, else fetch all SyncResult result = syncResults; for (Class sClass : modelsRegisteredForSync) { try { result = doFetchAllForURL(sClass, syncResults); } catch (Exception ex) { } } postAllDirtyRecordsToServer(result); } private void downloadDeltaForAllRegisteredModels() { for (Class sClass : modelsRegisteredForSync) { try { //doFetchAllForURL(sClass); } catch (Exception ex) { } } } private void postAllDirtyRecordsToServer(SyncResult syncResult) { SyncResult result = syncResult; for (Class sClass : modelsRegisteredForSync) { try { Class[] cArg = new Class[1]; cArg[0] = Context.class; Method methodForGettingDirtyObjects = sClass.getDeclaredMethod("fetchAllDirtyObjectsInDB", cArg); ArrayList<Object> objectsToBeUpdated = (ArrayList<Object>) methodForGettingDirtyObjects.invoke(null, getContext()); for (Object obj : objectsToBeUpdated) { updateObjectAtServerForObject(obj, syncResult); } } catch (Exception ex) { System.out.println(ex.toString()); } } postAllNewlyCreatedRecordsToServer(result); } private void postAllNewlyCreatedRecordsToServer(SyncResult syncResult) { SyncResult result = syncResult; for (Class sClass : modelsRegisteredForSync) { try { Class[] cArg = new Class[1]; cArg[0] = Context.class; Method methodForNewlyCreatedObjects = sClass.getDeclaredMethod("fetchAllNewObjectsInDB", cArg); ArrayList<Object> objectsToBeCreated = (ArrayList<Object>) methodForNewlyCreatedObjects.invoke(null, getContext()); for (Object obj : objectsToBeCreated) { createNewObjectAtServerForObject(obj, syncResult); } } catch (Exception ex) { System.out.println(ex.toString()); } } deleteAllDeletedRecordsAtServer(result); } private void deleteAllDeletedRecordsAtServer(SyncResult syncResult) { for (Class sClass : modelsRegisteredForSync) { try { Class[] cArg = new Class[1]; cArg[0] = Context.class; Method methodToGetDeletedObjects = sClass.getDeclaredMethod("fetchAllDeletedObjectsInDB", cArg); ArrayList<Object> objectsToBeDeleted = (ArrayList<Object>) methodToGetDeletedObjects.invoke(null, getContext()); for (Object obj : objectsToBeDeleted) { deleteObjectAtServerForObject(obj, syncResult); } } catch (Exception ex) { System.out.println(ex.toString()); } } syncCompleted(); } private void syncCompleted() { } /** * Given a string representation of a URL, sets up a connection and gets an input stream. */ private SyncResult doFetchAllForURL(Class sClass, SyncResult sResults) throws IOException { try { Method urlForFetchAllMethod = sClass.getDeclaredMethod("urlForFetchAll", null); String urlForFetchAll = (String) urlForFetchAllMethod.invoke(null, null); System.out.println(urlForFetchAll); final URL fetchAllURL = new URL(urlForFetchAll); HttpURLConnection conn = (HttpURLConnection) fetchAllURL.openConnection(); conn.setRequestProperty("X-Parse-Application-Id", "TsEDR12ICJtD59JM92WslVurN0wh5JPuznKvroRc"); conn.setRequestProperty("X-Parse-REST-API-Key", "4LC6oFNCyqLMFHSdPIPsxJoXHY6gTHGMG2kUcbwB"); conn.setReadTimeout(NET_READ_TIMEOUT_MILLIS /* milliseconds */); conn.setConnectTimeout(NET_CONNECT_TIMEOUT_MILLIS /* milliseconds */); conn.setRequestMethod("GET"); conn.setDoInput(true); // Starts the query conn.connect(); Log.i(TAG, "Response from parse.com : " + conn.getResponseMessage()); Log.i(TAG, "Status Code from parse.com : " + conn.getResponseCode()); Class[] cArg = new Class[3]; cArg[0] = Context.class; cArg[1] = InputStream.class; cArg[2] = SyncResult.class; Method handleDataForModel = sClass.getDeclaredMethod("handleInsertWithData", cArg); SyncResult objectsUpdated = (SyncResult) handleDataForModel.invoke(null, this.getContext(), conn.getInputStream(), sResults); return objectsUpdated; } catch (Exception ex) { Log.i(TAG, "exception " + ex.toString()); } return sResults; } private SyncResult createNewObjectAtServerForObject(Object obj, SyncResult sResults) { try { System.out.println(obj.getClass()); Method methodForCreateNewServerObjectURL = obj.getClass().getDeclaredMethod("urlForNewServerObject", null); String urlForNewServerObject = (String) methodForCreateNewServerObjectURL.invoke(obj, null); Method methodForPostBodyForUpdateServerObject = obj.getClass() .getDeclaredMethod("postBodyForNewServerObject", null); String postBodyForUpdateServerObject = (String) methodForPostBodyForUpdateServerObject.invoke(obj, null); System.out.println(urlForNewServerObject); InputStream inputStream = null; String result = ""; HttpClient httpclient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(urlForNewServerObject); StringEntity se = new StringEntity(postBodyForUpdateServerObject); httpPost.setEntity(se); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-type", "application/json"); httpPost.setHeader("X-Parse-Application-Id", "TsEDR12ICJtD59JM92WslVurN0wh5JPuznKvroRc"); httpPost.setHeader("X-Parse-REST-API-Key", "4LC6oFNCyqLMFHSdPIPsxJoXHY6gTHGMG2kUcbwB"); HttpResponse httpResponse = httpclient.execute(httpPost); inputStream = httpResponse.getEntity().getContent(); if (inputStream != null) { result = convertInputStreamToString(inputStream); System.out.println(result); if (httpResponse.getStatusLine().getStatusCode() == 201) { Class[] cArg = new Class[2]; cArg[0] = String.class; cArg[1] = Context.class; Method handleInsertResponseForObjectMethod = obj.getClass() .getDeclaredMethod("handleObjectCreateResponseFromServer", cArg); String setSynced = (String) handleInsertResponseForObjectMethod.invoke(obj, result, getContext()); if (setSynced.equals("1")) System.out.println("Success!!"); } } else result = "Did not work!"; } catch (Exception ex) { Log.i(TAG, "exception " + ex.toString()); } return sResults; } private SyncResult updateObjectAtServerForObject(Object obj, SyncResult sResults) { try { System.out.println(obj.getClass()); Method methodForUpdateServerObjectURL = obj.getClass().getDeclaredMethod("urlForUpdateServerObject", null); String urlForUpdateServerObject = (String) methodForUpdateServerObjectURL.invoke(obj, null); Method methodForPostBodyForUpdateServerObject = obj.getClass() .getDeclaredMethod("postBodyForUpdateServerObject", null); String postBodyForUpdateServerObject = (String) methodForPostBodyForUpdateServerObject.invoke(obj, null); System.out.println(urlForUpdateServerObject); InputStream inputStream = null; String result = ""; HttpClient httpclient = new DefaultHttpClient(); HttpPut httpPost = new HttpPut(urlForUpdateServerObject); StringEntity se = new StringEntity(postBodyForUpdateServerObject); httpPost.setEntity(se); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-type", "application/json"); httpPost.setHeader("X-Parse-Application-Id", "TsEDR12ICJtD59JM92WslVurN0wh5JPuznKvroRc"); httpPost.setHeader("X-Parse-REST-API-Key", "4LC6oFNCyqLMFHSdPIPsxJoXHY6gTHGMG2kUcbwB"); HttpResponse httpResponse = httpclient.execute(httpPost); inputStream = httpResponse.getEntity().getContent(); if (inputStream != null) { result = convertInputStreamToString(inputStream); System.out.println(result); if (httpResponse.getStatusLine().getStatusCode() == 200) { Class[] cArg = new Class[2]; cArg[0] = String.class; cArg[1] = Context.class; Method handleObjectUpdateResponseFromServerUpdateResponseForObjectMethod = obj.getClass() .getDeclaredMethod("handleObjectUpdateResponseFromServer", cArg); String setSynced = (String) handleObjectUpdateResponseFromServerUpdateResponseForObjectMethod .invoke(obj, result, getContext()); if (setSynced.equals("1")) System.out.println("Success!!"); } } else result = "Did not work!"; } catch (Exception ex) { Log.i(TAG, "exception " + ex.toString()); } return sResults; } private SyncResult deleteObjectAtServerForObject(Object obj, SyncResult sResults) { try { Method methodForDeleteServerObject = obj.getClass().getDeclaredMethod("urlForDeleteServerObject", null); String urlForDeleteServerObject = (String) methodForDeleteServerObject.invoke(obj, null); System.out.println(urlForDeleteServerObject); final URL fetchAllURL = new URL(urlForDeleteServerObject); InputStream inputStream = null; String result = ""; HttpClient httpclient = new DefaultHttpClient(); HttpDelete httpDelete = new HttpDelete(urlForDeleteServerObject); httpDelete.setHeader("Accept", "application/json"); httpDelete.setHeader("Content-type", "application/json"); httpDelete.setHeader("X-Parse-Application-Id", "TsEDR12ICJtD59JM92WslVurN0wh5JPuznKvroRc"); httpDelete.setHeader("X-Parse-REST-API-Key", "4LC6oFNCyqLMFHSdPIPsxJoXHY6gTHGMG2kUcbwB"); HttpResponse httpResponse = httpclient.execute(httpDelete); inputStream = httpResponse.getEntity().getContent(); if (inputStream != null) { result = convertInputStreamToString(inputStream); System.out.println(result); if (httpResponse.getStatusLine().getStatusCode() == 200) { Class[] cArg = new Class[2]; cArg[0] = String.class; cArg[1] = Context.class; Method handleObjectDeleteResponseFromServer = obj.getClass() .getDeclaredMethod("handleObjectDeleteResponseFromServer", cArg); String setSynced = (String) handleObjectDeleteResponseFromServer.invoke(obj, result, getContext()); if (setSynced.equals("1")) System.out.println("Success!!"); } } } catch (Exception ex) { Log.i(TAG, "exception " + ex.toString()); } return sResults; } private void updateDataBaseForModel(Class syncModel, SyncResult syncResults, InputStream stream) { //1. parse data and get array of objects. //2. determine what kind of class it is //3. start iterating the parsed data and check for previous objects with same id. //4. if it does'nt exist then directly insert. //5. if it exists then check its syncFlag. //6. if syncFlag is 0 then force update. //7. if syncFlag is anything other than 0 then mark that record as conflicted. //8. go to next record. try { } catch (Exception ex) { } } private static String convertInputStreamToString(InputStream inputStream) throws IOException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; String result = ""; while ((line = bufferedReader.readLine()) != null) result += line; inputStream.close(); return result; } }