/** * 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 2/1/15 4:15 PM */ package com.odoo.core.service; import android.content.Context; import android.content.SyncResult; import android.util.Log; 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.support.OdooFields; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OListUtils; import com.odoo.core.utils.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import odoo.ODomain; import odoo.Odoo; public class OSyncDataUtils { public static final String TAG = OSyncDataUtils.class.getSimpleName(); private Context mContext; private OModel mModel; private OUser mUser; private JSONObject response; private HashSet<String> recordsId = new HashSet<>(); private HashMap<String, SyncRelationRecords> relationRecordsHashMap = new HashMap<>(); private Odoo mOdoo; private SyncResult mResult; private HashMap<String, List<Integer>> updateToServerRecords = new HashMap<>(); private Boolean mCreateRelationRecords = true; public OSyncDataUtils(Context context, Odoo odoo, OModel model, OUser user, JSONObject response, SyncResult result, Boolean createRelRecord) { mContext = context; mOdoo = odoo; mModel = model; mUser = user; this.response = response; mResult = result; mCreateRelationRecords = createRelRecord; JSONArray updateInLocal = checkLocalUpdatedRecords(); handleResult(updateInLocal); } private JSONArray checkLocalUpdatedRecords() { // Array of records which are new or need to update in local JSONArray finalRecords = new JSONArray(); try { // Getting list of ids which are present in local database List<Integer> serverIds = new ArrayList<>(); HashMap<String, JSONObject> serverIdRecords = new HashMap<>(); JSONArray records = response.getJSONArray("records"); for (int i = 0; i < records.length(); i++) { JSONObject record = records.getJSONObject(i); if (mModel.hasServerRecord(record.getInt("id")) && mModel.isServerRecordDirty(record.getInt("id"))) { int server_id = record.getInt("id"); serverIds.add(server_id); serverIdRecords.put("key_" + server_id, record); } else { finalRecords.put(record); } } // getting local dirty records if server records length = 0 if (records.length() <= 0) { for (ODataRow row : mModel.select(new String[]{}, "_is_dirty = ? and _is_active = ? and id != ?", new String[]{"true", "true", "0"})) { serverIds.add(row.getInt("id")); } } // Comparing dirty (updated) record List<Integer> updateToServerIds = new ArrayList<>(); if (serverIds.size() > 0) { HashMap<String, String> write_dates = getWriteDate(mModel, serverIds); for (Integer server_id : serverIds) { String key = "key_" + server_id; String write_date = write_dates.get(key); ODataRow record = mModel.browse(new String[]{"_write_date"}, "id = ?", new String[]{server_id + ""}); if (record != null) { Date write_date_obj = ODateUtils.createDateObject(write_date, ODateUtils.DEFAULT_FORMAT, false); Date _write_date_obj = ODateUtils.createDateObject(record.getString("_write_date"), ODateUtils.DEFAULT_FORMAT, false); if (_write_date_obj.compareTo(write_date_obj) > 0) { // Local record is latest updateToServerIds.add(server_id); } else { if (serverIdRecords.containsKey(key)) { finalRecords.put(serverIdRecords.get(key)); } } } } } if (updateToServerIds.size() > 0) { updateToServerRecords.put(mModel.getModelName(), updateToServerIds); } } catch (Exception e) { e.printStackTrace(); } return finalRecords; } private HashMap<String, String> getWriteDate(OModel model, List<Integer> ids) { HashMap<String, String> map = new HashMap<>(); try { JSONArray result; if (model.getColumn("write_date") != null) { OdooFields fields = new OdooFields(new String[]{"write_date"}); ODomain domain = new ODomain(); domain.add("id", "in", ids); JSONObject data = mOdoo.search_read(model.getModelName(), fields.get(), domain.get()); result = data.getJSONArray("records"); } else { JSONObject data = mOdoo.perm_read(model.getModelName(), ids); result = data.getJSONArray("result"); } if (result.length() > 0) { for (int i = 0; i < result.length(); i++) { JSONObject obj = result.getJSONObject(i); map.put("key_" + obj.getInt("id"), obj.getString("write_date")); } } } catch (Exception e) { e.printStackTrace(); } return map; } private void handleResult(JSONArray records) { try { recordsId.clear(); int length = records.length(); int counter = 0; List<OColumn> columns = mModel.getColumns(false); columns.addAll(mModel.getFunctionalColumns()); for (int i = 0; i < length; i++) { JSONObject record = records.getJSONObject(i); OValues values = new OValues(); recordsId.add(mModel.getModelName() + "_" + record.getInt("id")); for (OColumn column : columns) { String name = column.getName(); if (column.getRelationType() == null) { // checks for functional store fields if (column.isFunctionalColumn() && column.canFunctionalStore()) { List<String> depends = column.getFunctionalStoreDepends(); OValues dependValues = new OValues(); if (!column.isLocal()) dependValues.put(column.getName(), record.get(column.getName())); for (String depend : depends) { if (record.has(depend)) { dependValues.put(depend, record.get(depend)); } } Object value = mModel.getFunctionalMethodValue(column, dependValues); values.put(column.getName(), value); } else { // Normal Columns values.put(name, record.get(name)); } } else { // Relation Columns if (!(record.get(name) instanceof Boolean)) { switch (column.getRelationType()) { case ManyToOne: JSONArray m2oData = record.getJSONArray(name); OModel m2o_model = mModel.createInstance(column.getType()); String recKey = m2o_model.getModelName() + "_" + m2oData.get(0); int m2oRowId; if (!recordsId.contains(recKey)) { OValues m2oValue = new OValues(); m2oValue.put("id", m2oData.get(0)); m2oValue.put(m2o_model.getDefaultNameColumn(), m2oData.get(1)); m2oValue.put("_is_dirty", "false"); m2oRowId = m2o_model.insertOrUpdate(m2oData.getInt(0), m2oValue); } else { m2oRowId = m2o_model.selectRowId(m2oData.getInt(0)); } values.put(name, m2oRowId); if (mCreateRelationRecords) { // Add id to sync if model contains more than (id,name) columns if (m2o_model.getColumns(false).size() > 2 || (m2o_model.getColumns(false).size() > 4 && mModel.getOdooVersion().getVersion_number() > 7)) { List<Integer> m2oIds = new ArrayList<>(); m2oIds.add(m2oData.getInt(0)); addUpdateRelationRecord(mModel, m2o_model.getTableName(), column.getType(), name, null, column.getRelationType(), m2oIds); } } m2o_model.close(); break; case ManyToMany: OModel m2mModel = mModel.createInstance(column.getType()); List<Integer> m2mIds = JSONUtils.<Integer>toList(record.getJSONArray(name)); if (mCreateRelationRecords) { addUpdateRelationRecord(mModel, m2mModel.getTableName(), column.getType(), name, null, column.getRelationType(), (column.getRecordSyncLimit() > 0) ? m2mIds.subList(0, column.getRecordSyncLimit()) : m2mIds); } List<Integer> m2mRowIds = new ArrayList<>(); for (Integer id : m2mIds) { recKey = m2mModel.getModelName() + "_" + id; int r_id; if (!recordsId.contains(recKey)) { OValues m2mValues = new OValues(); m2mValues.put("id", id); m2mValues.put("_is_dirty", "false"); r_id = m2mModel.insertOrUpdate(id, m2mValues); } else { r_id = m2mModel.selectRowId(id); } m2mRowIds.add(r_id); } if (m2mRowIds.size() > 0) { // Putting many to many related ids // (generated _id for each of server ids) values.put(name, m2mRowIds); } m2mModel.close(); break; case OneToMany: if (mCreateRelationRecords) { OModel o2mModel = mModel.createInstance(column.getType()); List<Integer> o2mIds = JSONUtils.<Integer>toList(record.getJSONArray(name)); addUpdateRelationRecord(mModel, o2mModel.getTableName(), column.getType(), name, column.getRelatedColumn(), column.getRelationType(), (column.getRecordSyncLimit() > 0) ? o2mIds.subList(0, column.getRecordSyncLimit()) : o2mIds); o2mModel.close(); } break; } } } } // Some default values values.put("id", record.getInt("id")); values.put("_write_date", ODateUtils.getUTCDate()); values.put("_is_active", "true"); values.put("_is_dirty", "false"); mModel.insertOrUpdate(record.getInt("id"), values); counter++; } Log.i(TAG, counter + " records affected"); } catch (Exception e) { e.printStackTrace(); } } public boolean updateRecordsOnServer(OSyncAdapter adapter) { try { // Use key (modal name) from updateToServerRecords // use updateToServerRecords ids int counter = 0; for (String key : updateToServerRecords.keySet()) { OModel model = OModel.get(mContext, key, mUser.getAndroidName()); List<String> ids = OListUtils.toStringList(updateToServerRecords.get(key)); counter += ids.size(); for (ODataRow record : model.select(null, "id IN ( " + StringUtils.repeat("?, ", ids.size() - 1) + " ?)", ids.toArray(new String[ids.size()]))) { if (adapter.validateRelationRecords(model, record)) { mOdoo.updateValues(model.getModelName(), JSONUtils.createJSONValues(model, record), record.getInt("id")); OValues value = new OValues(); value.put("_is_dirty", "false"); value.put("_write_date", ODateUtils.getUTCDate()); model.update(record.getInt(OColumn.ROW_ID), value); model.close(); } } } Log.i(TAG, counter + " records updated on server"); } catch (Exception e) { e.printStackTrace(); } return false; } private void addUpdateRelationRecord(OModel baseModel, String relTable, Class<?> model, String column, String relatedColumn, OColumn.RelationType type, List<Integer> ids) { String key = relTable + "_" + column; if (relationRecordsHashMap.containsKey(key)) { SyncRelationRecords data = relationRecordsHashMap.get(key); data.updateIds(ids); relationRecordsHashMap.put(key, data); } else { relationRecordsHashMap.put(key, new SyncRelationRecords(baseModel, model, column, relatedColumn, type, ids)); } } public HashMap<String, SyncRelationRecords> getRelationRecordsHashMap() { if (mCreateRelationRecords) return relationRecordsHashMap; return new HashMap<>(); } @Override protected void finalize() throws Throwable { super.finalize(); if (mModel != null) mModel.close(); } public static class SyncRelationRecords { private OModel baseModel; private Class<?> relationModel; private String relationColumn; private String relatedColumn; private OColumn.RelationType relationType; private List<Integer> serverIds = new ArrayList<>(); public SyncRelationRecords(OModel baseModel, Class<?> relationModel, String relationColumn, String relatedColumn, OColumn.RelationType relationType, List<Integer> serverIds) { this.baseModel = baseModel; this.relationModel = relationModel; this.relationColumn = relationColumn; this.relatedColumn = relatedColumn; this.relationType = relationType; this.serverIds.addAll(serverIds); } public OModel getBaseModel() { return baseModel; } public void setBaseModel(OModel baseModel) { this.baseModel = baseModel; } public Class<?> getRelationModel() { return relationModel; } public void setRelationModel(Class<?> relationModel) { this.relationModel = relationModel; } public String getRelationColumn() { return relationColumn; } public void setRelationColumn(String relationColumn) { this.relationColumn = relationColumn; } public String getRelatedColumn() { return relatedColumn; } public void setRelatedColumn(String relatedColumn) { this.relatedColumn = relatedColumn; } public OColumn.RelationType getRelationType() { return relationType; } public void setRelationType(OColumn.RelationType relationType) { this.relationType = relationType; } public List<Integer> getServerIds() { return serverIds; } public void setServerIds(List<Integer> serverIds) { this.serverIds.clear(); this.serverIds.addAll(serverIds); } public void updateIds(List<Integer> ids) { this.serverIds.addAll(ids); } public List<Integer> getUniqueIds() { List<Integer> ids = new ArrayList<>(); HashSet<Integer> uIds = new HashSet<>(serverIds); ids.addAll(uIds); return ids; } } }