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.versacomllc.audit.network.sync; import static com.versacomllc.audit.utils.Constants.FILE_UPLOAD_PATH; import static com.versacomllc.audit.utils.Constants.LOG_TAG; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URLConnection; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.DefaultHttpClient; import org.xmlpull.v1.XmlPullParserException; import android.accounts.Account; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.OperationApplicationException; import android.content.SyncResult; import android.os.Bundle; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.versacomllc.audit.data.DatabaseHandler; import com.versacomllc.audit.data.Employee; import com.versacomllc.audit.data.LocalAudit; import com.versacomllc.audit.data.LocalAuditDefect; import com.versacomllc.audit.data.LocalCustomer; import com.versacomllc.audit.data.LocalProject; import com.versacomllc.audit.data.LocalScopeOfWork; import com.versacomllc.audit.data.LocalScopeOfWorkTech; import com.versacomllc.audit.model.AuditDefect; import com.versacomllc.audit.model.Customer; import com.versacomllc.audit.model.Defect; import com.versacomllc.audit.model.InternalAudit; import com.versacomllc.audit.model.Project; import com.versacomllc.audit.model.ScopeOfWork; import com.versacomllc.audit.model.ScopeOfWorkTechnician; import com.versacomllc.audit.model.StringResponse; import com.versacomllc.audit.network.sync.provider.FeedContract; import com.versacomllc.audit.spice.GenericGetRequest; import com.versacomllc.audit.spice.GenericPostRequest; import com.versacomllc.audit.spice.GenericSpiceCallback; import com.versacomllc.audit.spice.RestCall; import com.versacomllc.audit.spice.SpiceRestHelper; import com.versacomllc.audit.utils.Constants; import com.versacomllc.audit.utils.EndPoints; import com.versacomllc.audit.utils.Utils; /** * 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"; protected SpiceRestHelper restHelper = new SpiceRestHelper(); /** * 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 = "http://android-developers.blogspot.com/atom.xml"; /** * 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; /** * Project used when querying content provider. Returns all known fields. */ private DatabaseHandler dbHandler = null; private static final String[] PROJECTION = new String[] { FeedContract.Entry._ID, FeedContract.Entry.COLUMN_NAME_ENTRY_ID, FeedContract.Entry.COLUMN_NAME_TITLE, FeedContract.Entry.COLUMN_NAME_LINK, FeedContract.Entry.COLUMN_NAME_PUBLISHED }; // 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(); dbHandler = new DatabaseHandler(context); } /** * Constructor. Obtains handle to content resolver for later use. */ public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); mContentResolver = context.getContentResolver(); dbHandler = new DatabaseHandler(context); } /** * 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 { Log.i(TAG, "Streaming data from network: "); /** Should sync when Internet connection is available */ if (Utils.isOnline(getContext())) { // Sync customer this.loadCustomerList(getContext()); // Add stie work types this.loadSiteWorkTypesList(getContext()); this.loadEmployeeList(getContext()); this.loadDefectList(getContext()); this.loadProjectList(getContext()); this.synchronizeAuditRecords(getContext()); } } catch (RuntimeException e) { Log.e(TAG, "Error updating database: " + e.toString()); syncResult.databaseError = true; return; } Log.i(TAG, "Network synchronization complete"); } private String uploadFile(String path) { String downloadPath = Constants.FILE_CONTENT_PATH; if (TextUtils.isEmpty(path)) { Log.d(LOG_TAG, "File path is empty."); return null; } HttpClient httpclient = new DefaultHttpClient(); try { File file = new File(path); HttpPost httppost = new HttpPost(FILE_UPLOAD_PATH); String mimeType = URLConnection.guessContentTypeFromName(path); FileBody bin = new FileBody(file, mimeType); MultipartEntity reqEntity = new MultipartEntity(); reqEntity.addPart(file.getName(), bin); httppost.setEntity(reqEntity); Log.i(TAG, "executing request " + httppost.getRequestLine()); HttpResponse response = httpclient.execute(httppost); HttpEntity resEntity = response.getEntity(); if (resEntity != null) { Log.i(TAG, "Response content length: " + resEntity.getContentLength()); } downloadPath = downloadPath + file.getName(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { httpclient.getConnectionManager().shutdown(); } catch (Exception ignore) { } } return downloadPath; } private void synchronizeAuditRecords(Context context) { final List<LocalAudit> lAudits = dbHandler.getAuditDao().getAllPendingInternalAudits(); if (lAudits == null || lAudits.size() == 0) { Log.d(LOG_TAG, "No new or changes records to submit on server"); return; } for (LocalAudit audit : lAudits) { if (TextUtils.isEmpty(audit.getRid()) || audit.getRid().equals("-1")) { loadChildRecords(audit); addLocalAuditToServer(audit, context); } else { loadChildRecords(audit); updateLocalAuditToServer(audit, context); } } } private void loadChildRecords(LocalAudit audit) { /** Scope of works */ List<LocalScopeOfWork> localScopeOfWorks = dbHandler.getScopeOfWorkDao() .getPendingScopeOfWorkByAuditId(audit.getId()); for (LocalScopeOfWork sow : localScopeOfWorks) { //Load Technician List<LocalScopeOfWorkTech> lTechs = dbHandler.getScopeOfWorkTechDao() .getPendingScopeOfWorkTechBySOWId(sow.getId()); sow.setlWorkTechs(lTechs); /** Audit defects */ List<LocalAuditDefect> lAuditDefects = dbHandler.getAuditDefectDao() .getPendingAuditDefectsBySowId(sow.getId()); sow.setlAuditDefects(lAuditDefects); //TODO UNCOMMENTS uploadAuditDefectPictures(lAuditDefects); } audit.setWorks(localScopeOfWorks); } private void uploadAuditDefectPictures(List<LocalAuditDefect> auditDefects) { for (LocalAuditDefect defect : auditDefects) { final String picBeforePath = defect.getDefectPicBefore(); final String picAfterPath = defect.getDefectPicAfter(); final String downloadPathB = uploadFile(picBeforePath); final String downloadPathA = uploadFile(picAfterPath); defect.setDefectPicBeforeOnServer(downloadPathB); defect.setDefectPicAfteOnServer(downloadPathA); } } private void addLocalAuditToServer(final LocalAudit audit, Context context) { String endPoint = EndPoints.REST_CALL_POST_AUDITS.getSimpleAddress(); GenericSpiceCallback<InternalAudit> callBackInterface = new GenericSpiceCallback<InternalAudit>(context) { @Override public void onSpiceSuccess(InternalAudit response) { if (response != null) { Log.d(LOG_TAG, "Response from the server: " + response.getRid()); updateResonse(audit, response); } } @Override public void onSpiceError(RestCall<InternalAudit> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<InternalAudit> restCall, int reason, Throwable exception) { } }; final String text = audit.toInternalAudit().toString(); Log.d(LOG_TAG, text); restHelper.execute( new GenericPostRequest<InternalAudit>(InternalAudit.class, endPoint, audit.toInternalAudit()), callBackInterface); } private void updateResonse(final LocalAudit audit, InternalAudit response) { audit.setRid(response.getRid()); audit.setSyn(1); dbHandler.getAuditDao().updateInternalAudit(audit); //Update Scope of works if (audit.getWorks() != null && response.getScopeOfWorks() != null) { for (int i = 0; i < audit.getWorks().size(); i++) { LocalScopeOfWork w = audit.getWorks().get(i); ScopeOfWork work = response.getScopeOfWorks().get(i); w.setRid(work.getRid()); w.setSync(1); dbHandler.getScopeOfWorkDao().updateSOW(w); //Update sow techs List<LocalScopeOfWorkTech> lTechs = w.getlWorkTechs(); if (lTechs != null) { for (int j = 0; j < lTechs.size(); j++) { LocalScopeOfWorkTech t = lTechs.get(j); ScopeOfWorkTechnician tt = work.getTechnicians().get(j); t.setRid(tt.getRid()); t.setSync(1); dbHandler.getScopeOfWorkTechDao().updateSOWTech(t); } } //Update audit defect List<LocalAuditDefect> lDefects = w.getlAuditDefects(); if (lDefects != null) { for (int j = 0; j < lDefects.size(); j++) { LocalAuditDefect d = lDefects.get(j); AuditDefect dd = work.getAuditDefects().get(j); d.setRid(dd.getRid()); d.setSync(1); dbHandler.getAuditDefectDao().updateAuditDefect(d); } } } List<LocalScopeOfWork> works = dbHandler.getScopeOfWorkDao() .getPendingScopeOfWorkByAuditId(audit.getId()); for (LocalScopeOfWork w : works) { Log.d(LOG_TAG, w.toString()); } } } private void updateLocalAuditToServer(final LocalAudit audit, Context context) { String endPoint = EndPoints.REST_CALL_POST_UPDATE_AUDIT.getAddress(audit.getRid()); GenericSpiceCallback<InternalAudit> callBackInterface = new GenericSpiceCallback<InternalAudit>(context) { @Override public void onSpiceSuccess(InternalAudit response) { if (response != null) { Log.d(LOG_TAG, "Response from the server: " + response.getRid()); updateResonse(audit, response); } } @Override public void onSpiceError(RestCall<InternalAudit> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<InternalAudit> restCall, int reason, Throwable exception) { } }; final String text = audit.toInternalAudit().toString(); Log.d(LOG_TAG, text); restHelper.execute( new GenericPostRequest<InternalAudit>(InternalAudit.class, endPoint, audit.toInternalAudit()), callBackInterface); } private void loadAuditList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_AUDITS.getSimpleAddress(); Log.d(TAG, "Importing audits;"); GenericSpiceCallback<InternalAudit[]> callBackInterface = new GenericSpiceCallback<InternalAudit[]>( context) { @Override public void onSpiceSuccess(InternalAudit[] response) { if (response != null) { Log.d(LOG_TAG, "Total records received from server: " + response.length); for (InternalAudit audit : response) { LocalAudit lAudit = new LocalAudit(audit); int rowEffected = dbHandler.getAuditDao().updateInternalAuditByRid(lAudit); if (rowEffected == 0) { dbHandler.getAuditDao().addInternalAudit(lAudit); } } } } @Override public void onSpiceError(RestCall<InternalAudit[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<InternalAudit[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<InternalAudit[]>(InternalAudit[].class, endPoint), callBackInterface); } private void loadEmployeeList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_EMPLOYEES.getSimpleAddress(); Log.d(TAG, "Importing employees;"); GenericSpiceCallback<Employee[]> callBackInterface = new GenericSpiceCallback<Employee[]>(context) { @Override public void onSpiceSuccess(Employee[] response) { if (response != null) { dbHandler.getEmployeeDao().addEmployeeList(Arrays.asList(response)); } } @Override public void onSpiceError(RestCall<Employee[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<Employee[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<Employee[]>(Employee[].class, endPoint), callBackInterface); } private void loadProjectList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_RPOJECTS.getSimpleAddress(); Log.d(TAG, "Importing PROJECTS;"); GenericSpiceCallback<Project[]> callBackInterface = new GenericSpiceCallback<Project[]>(context) { @Override public void onSpiceSuccess(Project[] response) { if (response != null) { dbHandler.getProjectDao().addProjectList(Arrays.asList(response)); } } @Override public void onSpiceError(RestCall<Project[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<Project[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<Project[]>(Project[].class, endPoint), callBackInterface); } private void loadDefectList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_DEFECTS.getSimpleAddress(); Log.d(TAG, "Importing defect list;"); GenericSpiceCallback<Defect[]> callBackInterface = new GenericSpiceCallback<Defect[]>(context) { @Override public void onSpiceSuccess(Defect[] response) { if (response != null) { dbHandler.getDefectDao().addDefectList(Arrays.asList(response)); } } @Override public void onSpiceError(RestCall<Defect[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<Defect[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<Defect[]>(Defect[].class, endPoint), callBackInterface); } private void loadSiteWorkTypesList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_SITE_WORK_TYPES.getSimpleAddress(); Log.d(TAG, "Importing site work type;"); GenericSpiceCallback<String[]> callBackInterface = new GenericSpiceCallback<String[]>(context) { @Override public void onSpiceSuccess(String[] response) { if (response != null) { dbHandler.getSiteWorkDao().addWorkTypes(Arrays.asList(response)); } } @Override public void onSpiceError(RestCall<String[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<String[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<String[]>(String[].class, endPoint), callBackInterface); } /** * @param context */ private void loadCustomerList(Context context) { String endPoint = EndPoints.REST_CALL_GET_QBASE_CUSTOMERS.getSimpleAddress(); List<LocalCustomer> customers = dbHandler.getAllCustomers(); final Map<String, LocalCustomer> customerMap = new HashMap<String, LocalCustomer>(); for (LocalCustomer customer : customers) { customerMap.put(customer.getRid(), customer); } GenericSpiceCallback<Customer[]> callBackInterface = new GenericSpiceCallback<Customer[]>(context) { @Override public void onSpiceSuccess(Customer[] response) { if (response != null) { for (Customer customer : response) { if (!customerMap.containsKey(customer.getRid())) { Log.d(TAG, " Adding customer to local db with rid: " + customer.getRid()); dbHandler.addCustomer(new LocalCustomer(customer)); } } } } @Override public void onSpiceError(RestCall<Customer[]> restCall, StringResponse response) { } @Override public void onSpiceError(RestCall<Customer[]> restCall, int reason, Throwable exception) { } }; restHelper.execute(new GenericGetRequest<Customer[]>(Customer[].class, endPoint), callBackInterface); } /** * 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 { } }