com.odoo.core.service.OSyncAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.odoo.core.service.OSyncAdapter.java

Source

/**
 * Odoo, Open Source Management Solution
 * Copyright (C) 2012-today Odoo SA (<http:www.odoo.com>)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http:www.gnu.org/licenses/>
 *
 * Created on 1/1/15 3:17 PM
 */
package com.odoo.core.service;

import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.util.Log;

import com.odoo.App;
import com.odoo.R;
import com.odoo.base.addons.ir.IrModel;
import com.odoo.base.addons.res.ResCompany;
import com.odoo.core.account.OdooAccountQuickManage;
import com.odoo.core.auth.OdooAccountManager;
import com.odoo.core.orm.ODataRow;
import com.odoo.core.orm.OModel;
import com.odoo.core.orm.OValues;
import com.odoo.core.orm.fields.OColumn;
import com.odoo.core.support.OUser;
import com.odoo.core.utils.JSONUtils;
import com.odoo.core.utils.ODateUtils;
import com.odoo.core.utils.OPreferenceManager;
import com.odoo.core.utils.OResource;
import com.odoo.core.utils.notification.ONotificationBuilder;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import odoo.ODomain;
import odoo.Odoo;
import odoo.OdooInstance;
import odoo.OdooSessionExpiredException;

public class OSyncAdapter extends AbstractThreadedSyncAdapter {
    public static final String TAG = OSyncAdapter.class.getSimpleName();
    public static final Integer REQUEST_SIGN_IN_ERROR = 11244;
    public static final String KEY_AUTH_ERROR = "key_authentication_error";
    private Context mContext;
    private Class<? extends OModel> mModelClass;
    private OModel mModel;
    private OSyncService mService;
    private OUser mUser;
    private Boolean checkForWriteCreateDate = true;
    private Integer mSyncDataLimit = 0;
    private HashMap<String, ODomain> mDomain = new HashMap<>();
    private OPreferenceManager preferenceManager;
    private Odoo mOdoo;
    private HashMap<String, ISyncFinishListener> mSyncFinishListeners = new HashMap<>();
    private App app = null;

    public OSyncAdapter(Context context, Class<? extends OModel> model, OSyncService service,
            boolean autoInitialize) {
        super(context, autoInitialize);
        init(context, model, service);
    }

    public OSyncAdapter(Context context, Class<? extends OModel> model, OSyncService service,
            boolean autoInitialize, boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        init(context, model, service);
    }

    private void init(Context context, Class<? extends OModel> model, OSyncService service) {
        mContext = context;
        mModelClass = model;
        mService = service;
        preferenceManager = new OPreferenceManager(mContext);
        app = (App) context.getApplicationContext();
    }

    public OSyncAdapter setDomain(ODomain domain) {
        mDomain.put(mModel.getModelName(), domain);
        return this;
    }

    public OSyncAdapter checkForWriteCreateDate(Boolean check) {
        checkForWriteCreateDate = check;
        return this;
    }

    public OSyncAdapter syncDataLimit(Integer dataLimit) {
        mSyncDataLimit = dataLimit;
        return this;
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
            SyncResult syncResult) {
        // Creating model Object
        mModel = new OModel(mContext, null, OdooAccountManager.getDetails(mContext, account.name))
                .createInstance(mModelClass);
        mUser = mModel.getUser();
        // Creating Odoo instance
        mOdoo = createOdooInstance(mContext, mUser);
        Log.i(TAG, "User        : " + mModel.getUser().getAndroidName());
        Log.i(TAG, "Model       : " + mModel.getModelName());
        Log.i(TAG, "Database    : " + mModel.getDatabaseName());
        Log.i(TAG, "Odoo Version: " + mUser.getVersion_number());
        // Calling service callback
        if (mService != null)
            mService.performDataSync(this, extras, mUser);

        //Creating domain
        ODomain domain = (mDomain.containsKey(mModel.getModelName())) ? mDomain.get(mModel.getModelName()) : null;

        // Ready for sync data from server
        syncData(mModel, mUser, domain, syncResult, true, true);
    }

    private void syncData(OModel model, OUser user, ODomain domain_filter, SyncResult result,
            Boolean checkForDataLimit, Boolean createRelationRecords) {
        Log.v(TAG, "Sync for (" + model.getModelName() + ") Started at " + ODateUtils.getDate());
        model.onSyncStarted();
        try {
            ODomain domain = new ODomain();
            domain.append(model.defaultDomain());
            if (domain_filter != null) {
                domain.append(domain_filter);
            }

            if (checkForWriteCreateDate) {
                List<Integer> serverIds = model.getServerIds();
                // Model Create date domain filters
                if (model.checkForCreateDate() && checkForDataLimit) {
                    if (serverIds.size() > 0) {
                        if (model.checkForWriteDate() && !model.isEmptyTable()) {
                            domain.add("|");
                        }
                        if (model.checkForWriteDate() && !model.isEmptyTable() && createRelationRecords
                                && model.getLastSyncDateTime() != null)
                            domain.add("&");
                    }
                    int data_limit = preferenceManager.getInt("sync_data_limit", 60);
                    domain.add("create_date", ">=", ODateUtils.getDateBefore(data_limit));
                    if (serverIds.size() > 0) {
                        domain.add("id", "not in", new JSONArray(serverIds.toString()));
                    }
                }
                // Model write date domain filters
                if (model.checkForWriteDate() && !model.isEmptyTable() && createRelationRecords) {
                    String last_sync_date = model.getLastSyncDateTime();
                    if (last_sync_date != null) {
                        domain.add("write_date", ">", last_sync_date);
                    }
                }
            }
            // Getting data
            JSONObject response = mOdoo.search_read(model.getModelName(), getFields(model), domain.get(), 0,
                    mSyncDataLimit, "create_date", "DESC");
            OSyncDataUtils dataUtils = new OSyncDataUtils(mContext, mOdoo, model, user, response, result,
                    createRelationRecords);
            // Updating records on server if local are latest updated.
            // if model allowed update record to server
            if (model.allowUpdateRecordOnServer()) {
                dataUtils.updateRecordsOnServer(this);
            }

            // Creating or updating relation records
            handleRelationRecords(user, dataUtils.getRelationRecordsHashMap(), result);

            // If model allowed to create record on server
            if (model.allowCreateRecordOnServer()) {
                createRecordsOnServer(model);
            }

            // If model allowed to delete record on server
            if (model.allowDeleteRecordOnServer()) {
                removeRecordOnServer(model);
            }

            // If model allowed to delete server removed record from local database
            if (model.allowDeleteRecordInLocal()) {
                removeNonExistRecordFromLocal(model);
            }

            Log.v(TAG, "Sync for (" + model.getModelName() + ") finished at " + ODateUtils.getDate());
            if (createRelationRecords) {
                IrModel irModel = new IrModel(mContext, user);
                irModel.setLastSyncDateTimeToNow(model);
            }
            model.onSyncFinished();
        } catch (OdooSessionExpiredException odooSession) {
            app.setOdoo(null, user);
            if (user.isOAauthLogin()) {
                // FIXME: Saas not working with multi login. Facing issue of session expired.
            } else {
                showSignInErrorNotification(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // Performing next sync if any in service
        if (mSyncFinishListeners.containsKey(model.getModelName())) {
            OSyncAdapter adapter = mSyncFinishListeners.get(model.getModelName()).performNextSync(user, result);
            mSyncFinishListeners.remove(model.getModelName());
            if (adapter != null) {
                SyncResult syncResult = new SyncResult();
                OModel syncModel = model.createInstance(adapter.getModelClass());
                ContentProviderClient contentProviderClient = mContext.getContentResolver()
                        .acquireContentProviderClient(syncModel.authority());
                adapter.onPerformSync(user.getAccount(), null, syncModel.authority(), contentProviderClient,
                        syncResult);
            }
        }
        model.close();
    }

    private void showSignInErrorNotification(OUser user) {
        ONotificationBuilder builder = new ONotificationBuilder(mContext, REQUEST_SIGN_IN_ERROR);
        builder.setTitle("Odoo authentication problem");
        builder.setBigText("May be you have changed your account " + "password or your account does not exists");
        builder.setIcon(R.drawable.ic_action_alert_warning);
        builder.setText(user.getAndroidName());
        builder.allowVibrate(true);
        builder.withRingTone(false);
        builder.setOngoing(true);
        builder.withLargeIcon(false);
        builder.setColor(OResource.color(mContext, R.color.android_orange_dark));
        Bundle extra = user.getAsBundle();
        // Actions
        ONotificationBuilder.NotificationAction actionReset = new ONotificationBuilder.NotificationAction(
                R.drawable.ic_action_refresh, "Reset", 110, "reset_password", OdooAccountQuickManage.class, extra);
        ONotificationBuilder.NotificationAction deleteAccount = new ONotificationBuilder.NotificationAction(
                R.drawable.ic_action_navigation_close, "Remove", 111, "remove_account",
                OdooAccountQuickManage.class, extra);
        builder.addAction(actionReset);
        builder.addAction(deleteAccount);
        builder.build().show();
    }

    private void handleRelationRecords(OUser user,
            HashMap<String, OSyncDataUtils.SyncRelationRecords> relationRecords, SyncResult result) {
        for (String key : relationRecords.keySet()) {
            OSyncDataUtils.SyncRelationRecords record = relationRecords.get(key);
            OModel model = record.getBaseModel();
            OModel rel_model = model.createInstance(record.getRelationModel());
            model.close();
            ODomain domain = new ODomain();
            domain.add("id", "in", record.getUniqueIds());
            syncData(rel_model, user, domain, result, false, false);
            // Updating manyToOne record with their relation record row_id
            switch (record.getRelationType()) {
            case ManyToOne:
                // Nothing to do. Already added link with record relation
                break;
            case OneToMany:
                // Update related_column with base id's row_id for each of record ids
                String related_column = record.getRelatedColumn();
                for (Integer id : record.getUniqueIds()) {
                    OValues values = new OValues();
                    ODataRow rec = rel_model.browse(rel_model.selectRowId(id));
                    values.put(related_column, rec.getInt(related_column));
                    values.put("_is_dirty", "false");
                    rel_model.update(rel_model.selectRowId(id), values);
                }
                break;
            case ManyToMany:
                // Nothing to do. Already added relation records links
                break;
            }
            rel_model.close();
        }
    }

    public static Odoo createOdooInstance(Context context, OUser user) {
        try {
            App app = (App) context.getApplicationContext();
            Odoo odoo = app.getOdoo(user);
            if (odoo == null) {
                if (user.isOAauthLogin()) {
                    odoo = new Odoo(context, user.getInstanceUrl(), user.isAllowSelfSignedSSL());
                    OdooInstance instance = new OdooInstance();
                    instance.setInstanceUrl(user.getInstanceUrl());
                    instance.setDatabaseName(user.getInstanceDatabase());
                    instance.setClientId(user.getClientId());
                    odoo.oauth_authenticate(instance, user.getUsername(), user.getPassword());
                } else {
                    odoo = new Odoo(context, user.getHost(), user.isAllowSelfSignedSSL());
                    odoo.authenticate(user.getUsername(), user.getPassword(), user.getDatabase());
                }
                app.setOdoo(odoo, user);

                ResCompany company = new ResCompany(context, user);
                if (company.count("id = ? ", new String[] { user.getCompany_id() }) <= 0) {
                    ODataRow company_details = new ODataRow();
                    company_details.put("id", user.getCompany_id());
                    company.quickCreateRecord(company_details);
                }

            }
            return odoo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private JSONObject getFields(OModel model) {
        JSONObject fields = new JSONObject();
        try {
            for (OColumn column : model.getColumns(false)) {
                fields.accumulate("fields", column.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fields;
    }

    /**
     * Creates locally created record on server (id with zero)
     *
     * @param model model object
     */
    private void createRecordsOnServer(OModel model) {
        List<ODataRow> records = model.select(null, "(id = ? or id = ?)", new String[] { "0", "false" });
        int counter = 0;
        for (ODataRow record : records) {
            if (validateRelationRecords(model, record)) {
                /*
                 Need to check server id for record.
                 It is possible that record created on server by validating main record.
                 */
                if (model.selectServerId(record.getInt(OColumn.ROW_ID)) == 0) {
                    int id = createOnServer(model, JSONUtils.createJSONValues(model, record));
                    if (id != OModel.INVALID_ROW_ID) {
                        OValues values = new OValues();
                        values.put("id", id);
                        values.put("_is_dirty", "false");
                        values.put("_write_date", ODateUtils.getUTCDate());
                        model.update(record.getInt(OColumn.ROW_ID), values);
                        counter++;
                    } else {
                        Log.e(TAG, "Unable to create record on server.");
                    }
                }
            }
        }
        if (counter == records.size()) {
            Log.i(TAG, counter + " records created on server.");
        }
    }

    /**
     * Validate relation record for the record. And if relation record not created on server.
     * It will be created on server before syncing original record
     *
     * @param model
     * @param row
     * @return updatedRow
     */
    public boolean validateRelationRecords(OModel model, ODataRow row) {
        Log.d(TAG, "Validating relation records for record");
        // Check for relation local record
        for (OColumn column : model.getRelationColumns()) {
            OModel relModel = model.createInstance(column.getType());
            switch (column.getRelationType()) {
            case ManyToOne:
                if (!row.getString(column.getName()).equals("false")) {
                    ODataRow m2oRec = row.getM2ORecord(column.getName()).browse();
                    if (m2oRec.getInt("id") == 0) {
                        int new_id = relModel.getServerDataHelper()
                                .createOnServer(JSONUtils.createJSONValues(relModel, m2oRec));
                        updateRecordServerId(relModel, m2oRec.getInt(OColumn.ROW_ID), new_id);
                    }
                }
                break;
            case ManyToMany:
                List<ODataRow> m2mRecs = row.getM2MRecord(column.getName()).browseEach();
                if (!m2mRecs.isEmpty()) {
                    for (ODataRow m2mRec : m2mRecs) {
                        if (m2mRec.getInt("id") == 0) {
                            int new_id = relModel.getServerDataHelper()
                                    .createOnServer(JSONUtils.createJSONValues(relModel, m2mRec));
                            updateRecordServerId(relModel, m2mRec.getInt(OColumn.ROW_ID), new_id);
                        }
                    }
                }
                break;
            case OneToMany:
                List<ODataRow> o2mRecs = row.getM2MRecord(column.getName()).browseEach();
                if (!o2mRecs.isEmpty()) {
                    for (ODataRow o2mRec : o2mRecs) {
                        if (o2mRec.getInt("id") == 0) {
                            int new_id = relModel.getServerDataHelper()
                                    .createOnServer(JSONUtils.createJSONValues(relModel, o2mRec));
                            updateRecordServerId(relModel, o2mRec.getInt(OColumn.ROW_ID), new_id);
                        }
                    }
                }
                break;
            }
        }
        return true;
    }

    /**
     * Updating local record with server id
     *
     * @param model
     * @param row_id
     * @param server_id
     */
    private void updateRecordServerId(OModel model, int row_id, int server_id) {
        OValues values = new OValues();
        values.put("id", server_id);
        values.put("_is_dirty", "false");
        model.update(row_id, values);
    }

    private int createOnServer(OModel model, JSONObject values) {
        int id = OModel.INVALID_ROW_ID;
        try {
            if (values != null) {
                JSONObject response = mOdoo.createNew(model.getModelName(), values);
                id = response.getInt("result");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return id;
    }

    /**
     * Removes record on server if local record is not active
     *
     * @param model
     */
    private void removeRecordOnServer(OModel model) {
        List<ODataRow> records = model.select(new String[] {}, "id != ? and _is_active = ?",
                new String[] { "0", "false" });
        List<Integer> serverIds = new ArrayList<>();
        for (ODataRow record : records) {
            serverIds.add(record.getInt("id"));
        }
        if (serverIds.size() > 0) {
            if (removeRecordsFromServer(model, serverIds)) {
                int counter = model.deleteRecords(serverIds, true);
                Log.i(TAG, counter + " records removed from server and local database");
            } else {
                Log.e(TAG, "Unable to remove records from server");
            }
        }
    }

    private boolean removeRecordsFromServer(OModel model, List<Integer> serverIds) {
        try {
            mOdoo.unlink(model.getModelName(), serverIds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Removes non exist record from local database
     *
     * @param model
     */
    private void removeNonExistRecordFromLocal(OModel model) {
        List<Integer> ids = model.getServerIds();
        try {
            ODomain domain = new ODomain();
            domain.add("id", "in", new JSONArray(ids.toString()));
            JSONObject result = mOdoo.search_read(model.getModelName(), new JSONObject(), domain.get());
            JSONArray records = result.getJSONArray("records");
            if (records.length() > 0) {
                for (int i = 0; i < records.length(); i++) {
                    JSONObject record = records.getJSONObject(i);
                    ids.remove(ids.indexOf(record.getInt("id")));
                }
            }
            int removedCounter = 0;
            if (ids.size() > 0) {
                removedCounter = model.deleteRecords(ids, true);
            }
            Log.i(TAG, removedCounter + " Records removed from local database.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Class<? extends OModel> getModelClass() {
        return mModelClass;
    }

    public void setModel(OModel model) {
        mModel = model;
    }

    public OModel getModel() {
        return mModel;
    }

    public OSyncAdapter onSyncFinish(ISyncFinishListener syncFinish) {
        mSyncFinishListeners.put(mModel.getModelName(), syncFinish);
        return this;
    }
}