/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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 org.jkiss.dbeaver.ui.controls.resultset; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.core.CoreMessages; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.DBPMessageType; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.data.*; import org.jkiss.dbeaver.model.edit.DBEPersistAction; import org.jkiss.dbeaver.model.exec.*; import org.jkiss.dbeaver.model.impl.AbstractExecutionSource; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSDataContainer; import org.jkiss.dbeaver.model.struct.DBSDataManipulator; import org.jkiss.dbeaver.model.struct.DBSEntity; import org.jkiss.dbeaver.model.struct.rdb.DBSManipulationType; import org.jkiss.dbeaver.runtime.jobs.DataSourceJob; import org.jkiss.dbeaver.ui.DBeaverIcons; import org.jkiss.dbeaver.ui.UIIcon; import org.jkiss.dbeaver.ui.UIUtils; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.utils.CommonUtils; import java.util.*; /** * Result set data updater */ class ResultSetPersister { private static final Log log = Log.getLog(ResultSetPersister.class); /** * Data update listener */ interface DataUpdateListener { void onUpdate(boolean success); } class ExecutionSource implements DBCExecutionSource { private final DBSDataContainer dataContainer; public ExecutionSource(DBSDataContainer dataContainer) { this.dataContainer = dataContainer; } @Nullable @Override public DBSDataContainer getDataContainer() { return dataContainer; } @NotNull @Override public Object getExecutionController() { return viewer; } @Nullable @Override public Object getSourceDescriptor() { return ResultSetPersister.this; } } @NotNull private final ResultSetViewer viewer; @NotNull private final ResultSetModel model; @NotNull private final DBDAttributeBinding[] columns; private final List<ResultSetRow> deletedRows = new ArrayList<>(); private final List<ResultSetRow> addedRows = new ArrayList<>(); private final List<ResultSetRow> changedRows = new ArrayList<>(); private final Map<ResultSetRow, DBDRowIdentifier> rowIdentifiers = new LinkedHashMap<>(); private final List<DataStatementInfo> insertStatements = new ArrayList<>(); private final List<DataStatementInfo> deleteStatements = new ArrayList<>(); private final List<DataStatementInfo> updateStatements = new ArrayList<>(); private final List<DBEPersistAction> script = new ArrayList<>(); ResultSetPersister(@NotNull ResultSetViewer viewer) { this.viewer = viewer; this.model = viewer.getModel(); this.columns = model.getAttributes(); } /** * Applies changes. * @throws org.jkiss.dbeaver.DBException * @param monitor progress monitor * @param listener value listener */ boolean applyChanges(@Nullable DBRProgressMonitor monitor, boolean generateScript, @Nullable DataUpdateListener listener) throws DBException { collectChanges(); prepareDeleteStatements(); prepareInsertStatements(); prepareUpdateStatements(); return execute(monitor, generateScript, listener); } public boolean refreshInsertedRows() throws DBCException { if (!viewer.getModel().isSingleSource()) { return false; } if (addedRows.isEmpty()) { // Nothing to refresh return false; } final DBDRowIdentifier rowIdentifier = getDefaultRowIdentifier(); if (rowIdentifier == null || rowIdentifier.getAttributes().isEmpty()) { // No key - can't refresh return false; } DBCExecutionContext executionContext = viewer.getContainer().getExecutionContext(); if (executionContext == null) { throw new DBCException("No execution context"); } RowRefreshJob job = new RowRefreshJob(executionContext, viewer.getDataContainer(), rowIdentifier, addedRows); job.schedule(); return true; } public List<DBEPersistAction> getScript() { return script; } private void collectChanges() { deletedRows.clear(); addedRows.clear(); changedRows.clear(); for (ResultSetRow row : model.getAllRows()) { switch (row.getState()) { case ResultSetRow.STATE_NORMAL: if (row.isChanged()) { changedRows.add(row); } break; case ResultSetRow.STATE_ADDED: addedRows.add(row); break; case ResultSetRow.STATE_REMOVED: deletedRows.add(row); break; } } // Prepare rows for (ResultSetRow row : changedRows) { if (row.changes == null || row.changes.isEmpty()) { continue; } DBDAttributeBinding changedAttr = row.changes.keySet().iterator().next(); rowIdentifiers.put(row, changedAttr.getRowIdentifier()); } } private void prepareDeleteStatements() throws DBException { // Make delete statements DBDRowIdentifier rowIdentifier = getDefaultRowIdentifier(); if (rowIdentifier == null) { throw new DBCException("Internal error: can't find entity identifier, delete is not possible"); } for (ResultSetRow row : deletedRows) { DataStatementInfo statement = new DataStatementInfo(DBSManipulationType.DELETE, row, rowIdentifier.getEntity()); List<DBDAttributeBinding> keyColumns = rowIdentifier.getAttributes(); for (DBDAttributeBinding binding : keyColumns) { statement.keyAttributes.add( new DBDAttributeValue( binding, model.getCellValue(binding, row))); } deleteStatements.add(statement); } } private void prepareInsertStatements() throws DBException { // Make insert statements final DBSEntity table = viewer.getModel().getSingleSource(); if (table == null) { throw new DBCException("Internal error: can't get single entity metadata, insert is not possible"); } for (ResultSetRow row : addedRows) { DataStatementInfo statement = new DataStatementInfo(DBSManipulationType.INSERT, row, table); DBDAttributeBinding docAttr = model.getDocumentAttribute(); if (docAttr != null) { statement.keyAttributes.add(new DBDAttributeValue(docAttr, model.getCellValue(docAttr, row))); } else { for (int i = 0; i < columns.length; i++) { DBDAttributeBinding column = columns[i]; statement.keyAttributes.add(new DBDAttributeValue(column, model.getCellValue(column, row))); } } insertStatements.add(statement); } } private void prepareUpdateStatements() throws DBException { // Make statements for (ResultSetRow row : this.rowIdentifiers.keySet()) { if (row.changes == null) continue; DBDRowIdentifier rowIdentifier = this.rowIdentifiers.get(row); DBSEntity table = rowIdentifier.getEntity(); { DataStatementInfo statement = new DataStatementInfo(DBSManipulationType.UPDATE, row, table); // Updated columns for (DBDAttributeBinding changedAttr : row.changes.keySet()) { statement.updateAttributes.add( new DBDAttributeValue( changedAttr, model.getCellValue(changedAttr, row))); } // Key columns List<DBDAttributeBinding> idColumns = rowIdentifier.getAttributes(); for (DBDAttributeBinding metaColumn : idColumns) { Object keyValue = model.getCellValue(metaColumn, row); // Try to find old key oldValue if (row.changes != null && row.changes.containsKey(metaColumn)) { keyValue = row.changes.get(metaColumn); } statement.keyAttributes.add(new DBDAttributeValue(metaColumn, keyValue)); } updateStatements.add(statement); } } } private boolean execute(@Nullable DBRProgressMonitor monitor, boolean generateScript, @Nullable final DataUpdateListener listener) throws DBException { DBCExecutionContext executionContext = viewer.getContainer().getExecutionContext(); if (executionContext == null) { throw new DBCException("No execution context"); } DataUpdaterJob job = new DataUpdaterJob(generateScript, listener, executionContext); if (monitor == null) { job.schedule(); return true; } else { job.run(monitor); return job.getError() == null; } } public void rejectChanges() { collectChanges(); for (ResultSetRow row : changedRows) { if (row.changes != null) { for (Map.Entry<DBDAttributeBinding, Object> changedValue : row.changes.entrySet()) { Object curValue = model.getCellValue(changedValue.getKey(), row); // If new value and old value are the same - do not release it if (curValue != changedValue.getValue()) { DBUtils.releaseValue(curValue); model.updateCellValue(changedValue.getKey(), row, changedValue.getValue(), false); } } row.changes = null; } } boolean rowsChanged = model.cleanupRows(addedRows); // Remove deleted rows for (ResultSetRow row : deletedRows) { row.setState(ResultSetRow.STATE_NORMAL); } model.refreshChangeCount(); viewer.redrawData(false, rowsChanged); viewer.fireResultSetChange(); viewer.updateEditControls(); viewer.updatePanelsContent(false); viewer.getActivePresentation().updateValueView(); } // Reflect data changes in viewer // Changes affects only rows which statements executed successfully private boolean reflectChanges() { boolean rowsChanged = false; for (ResultSetRow row : changedRows) { for (DataStatementInfo stat : updateStatements) { if (stat.executed && stat.row == row) { reflectKeysUpdate(stat); row.changes = null; break; } } } for (ResultSetRow row : addedRows) { for (DataStatementInfo stat : insertStatements) { if (stat.executed && stat.row == row) { reflectKeysUpdate(stat); row.setState(ResultSetRow.STATE_NORMAL); break; } } } for (ResultSetRow row : deletedRows) { for (DataStatementInfo stat : deleteStatements) { if (stat.executed && stat.row == row) { model.cleanupRow(row); rowsChanged = true; break; } } } model.refreshChangeCount(); return rowsChanged; } private void reflectKeysUpdate(DataStatementInfo stat) { // Update keys if (!stat.updatedCells.isEmpty()) { for (Map.Entry<Integer, Object> entry : stat.updatedCells.entrySet()) { ResultSetRow row = stat.row; DBUtils.releaseValue(row.values[entry.getKey()]); row.values[entry.getKey()] = entry.getValue(); } } } @Nullable private DBDRowIdentifier getDefaultRowIdentifier() { for (int i = 0; i < columns.length; i++) { DBDRowIdentifier rowIdentifier = columns[0].getRowIdentifier(); if (rowIdentifier != null) { return rowIdentifier; } } return null; } @NotNull private DBSDataManipulator getDataManipulator(DBSEntity entity) throws DBCException { if (entity instanceof DBSDataManipulator) { return (DBSDataManipulator)entity; } else { throw new DBCException("Entity " + entity.getName() + " doesn't support data manipulation"); } } private class DataUpdaterJob extends DataSourceJob { private final boolean generateScript; private final DataUpdateListener listener; private boolean autocommit; private DBCStatistics updateStats, insertStats, deleteStats; private DBCSavepoint savepoint; private Throwable error; protected DataUpdaterJob(boolean generateScript, @Nullable DataUpdateListener listener, @NotNull DBCExecutionContext executionContext) { super(CoreMessages.controls_resultset_viewer_job_update, DBeaverIcons.getImageDescriptor(UIIcon.SQL_EXECUTE), executionContext); this.generateScript = generateScript; this.listener = listener; } public Throwable getError() { return error; } @Override protected IStatus run(DBRProgressMonitor monitor) { model.setUpdateInProgress(true); updateStats = new DBCStatistics(); insertStats = new DBCStatistics(); deleteStats = new DBCStatistics(); try { error = executeStatements(monitor); } finally { model.setUpdateInProgress(false); } if (!generateScript) { // Reflect changes DBeaverUI.syncExec(new Runnable() { @Override public void run() { boolean rowsChanged = false; if (DataUpdaterJob.this.autocommit || error == null) { rowsChanged = reflectChanges(); } if (!viewer.getControl().isDisposed()) { //releaseStatements(); viewer.redrawData(false, rowsChanged); viewer.updateEditControls(); if (error == null) { viewer.setStatus( NLS.bind( CoreMessages.controls_resultset_viewer_status_inserted_, new Object[]{ DataUpdaterJob.this.insertStats.getRowsUpdated(), DataUpdaterJob.this.deleteStats.getRowsUpdated(), DataUpdaterJob.this.updateStats.getRowsUpdated()})); } else { UIUtils.showErrorDialog(viewer.getSite().getShell(), "Data error", "Error synchronizing data with database", error); viewer.setStatus(GeneralUtils.getFirstMessage(error), DBPMessageType.ERROR); } } viewer.fireResultSetChange(); } }); if (this.listener != null) { this.listener.onUpdate(error == null); } } return Status.OK_STATUS; } private Throwable executeStatements(DBRProgressMonitor monitor) { DBCTransactionManager txnManager = DBUtils.getTransactionManager(getExecutionContext()); try (DBCSession session = getExecutionContext().openSession(monitor, DBCExecutionPurpose.UTIL, CoreMessages.controls_resultset_viewer_job_update)) { monitor.beginTask( CoreMessages.controls_resultset_viewer_monitor_aply_changes, ResultSetPersister.this.deleteStatements.size() + ResultSetPersister.this.insertStatements.size() + ResultSetPersister.this.updateStatements.size() + 1); if (!generateScript && txnManager != null) { monitor.subTask(CoreMessages.controls_resultset_check_autocommit_state); try { this.autocommit = txnManager.isAutoCommit(); } catch (DBCException e) { log.warn("Can't determine autocommit state", e); this.autocommit = true; } } monitor.worked(1); if (!generateScript && txnManager != null) { if (!this.autocommit && txnManager.supportsSavepoints()) { try { this.savepoint = txnManager.setSavepoint(monitor, null); } catch (Throwable e) { // May be savepoints not supported log.debug("Can't set savepoint", e); } } } try { for (DataStatementInfo statement : ResultSetPersister.this.deleteStatements) { if (monitor.isCanceled()) break; try { DBSDataManipulator dataContainer = getDataManipulator(statement.entity); DBSDataManipulator.ExecuteBatch batch = dataContainer.deleteData( session, DBDAttributeValue.getAttributes(statement.keyAttributes), new ExecutionSource(dataContainer)); try { batch.add(DBDAttributeValue.getValues(statement.keyAttributes)); if (generateScript) { batch.generatePersistActions(session, script); } else { deleteStats.accumulate(batch.execute(session)); } } finally { batch.close(); } processStatementChanges(statement); } catch (DBException e) { processStatementError(statement, session); return e; } monitor.worked(1); } for (DataStatementInfo statement : ResultSetPersister.this.insertStatements) { if (monitor.isCanceled()) break; try { DBSDataManipulator dataContainer = getDataManipulator(statement.entity); DBSDataManipulator.ExecuteBatch batch = dataContainer.insertData( session, DBDAttributeValue.getAttributes(statement.keyAttributes), statement.needKeys() ? new KeyDataReceiver(statement) : null, new ExecutionSource(dataContainer)); try { batch.add(DBDAttributeValue.getValues(statement.keyAttributes)); if (generateScript) { batch.generatePersistActions(session, script); } else { insertStats.accumulate(batch.execute(session)); } } finally { batch.close(); } processStatementChanges(statement); } catch (DBException e) { processStatementError(statement, session); return e; } monitor.worked(1); } for (DataStatementInfo statement : ResultSetPersister.this.updateStatements) { if (monitor.isCanceled()) break; try { DBSDataManipulator dataContainer = getDataManipulator(statement.entity); DBSDataManipulator.ExecuteBatch batch = dataContainer.updateData( session, DBDAttributeValue.getAttributes(statement.updateAttributes), DBDAttributeValue.getAttributes(statement.keyAttributes), null, new ExecutionSource(dataContainer)); try { // Make single array of values Object[] attributes = new Object[statement.updateAttributes.size() + statement.keyAttributes.size()]; for (int i = 0; i < statement.updateAttributes.size(); i++) { attributes[i] = statement.updateAttributes.get(i).getValue(); } for (int i = 0; i < statement.keyAttributes.size(); i++) { attributes[statement.updateAttributes.size() + i] = statement.keyAttributes.get(i).getValue(); } // Execute batch.add(attributes); if (generateScript) { batch.generatePersistActions(session, script); } else { updateStats.accumulate(batch.execute(session)); } } finally { batch.close(); } processStatementChanges(statement); } catch (DBException e) { processStatementError(statement, session); return e; } monitor.worked(1); } return null; } finally { if (!generateScript && txnManager != null && this.savepoint != null) { try { txnManager.releaseSavepoint(monitor, this.savepoint); } catch (Throwable e) { // Maybe savepoints not supported log.debug("Can't release savepoint", e); } } } } finally { monitor.done(); } } private void processStatementChanges(DataStatementInfo statement) { statement.executed = true; } private void processStatementError(DataStatementInfo statement, DBCSession session) { statement.executed = false; if (!generateScript) { DBCTransactionManager txnManager = DBUtils.getTransactionManager(getExecutionContext()); if (txnManager != null) { try { if (!txnManager.isAutoCommit()) { txnManager.rollback(session, savepoint); } } catch (Throwable e) { log.debug("Error during transaction rollback", e); } } } } } /** * Key data receiver */ class KeyDataReceiver implements DBDDataReceiver { DataStatementInfo statement; public KeyDataReceiver(DataStatementInfo statement) { this.statement = statement; } @Override public void fetchStart(DBCSession session, DBCResultSet resultSet, long offset, long maxRows) throws DBCException { } @Override public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException { DBCResultSetMetaData rsMeta = resultSet.getMeta(); List<DBCAttributeMetaData> keyAttributes = rsMeta.getAttributes(); for (int i = 0; i < keyAttributes.size(); i++) { DBCAttributeMetaData keyAttribute = keyAttributes.get(i); DBDValueHandler valueHandler = DBUtils.findValueHandler(session, keyAttribute); Object keyValue = valueHandler.fetchValueObject(session, resultSet, keyAttribute, i); if (keyValue == null) { // [MSSQL] Sometimes driver returns empty list of generated keys if // table has auto-increment columns and user performs simple row update // Just ignore such empty keys. We can't do anything with them anyway continue; } boolean updated = false; if (!CommonUtils.isEmpty(keyAttribute.getName())) { DBDAttributeBinding binding = model.getAttributeBinding(statement.entity, keyAttribute.getName()); if (binding != null) { // Got it. Just update column oldValue statement.updatedCells.put(binding.getOrdinalPosition(), keyValue); //curRows.get(statement.row.row)[colIndex] = keyValue; updated = true; } } if (!updated) { // Key not found // Try to find and update auto-increment column for (int k = 0; k < columns.length; k++) { DBDAttributeBinding column = columns[k]; if (column.isAutoGenerated()) { // Got it statement.updatedCells.put(k, keyValue); //curRows.get(statement.row.row)[k] = keyValue; updated = true; break; } } } if (!updated) { // Auto-generated key not found // Just skip it.. log.debug("Can't find target column for auto-generated key '" + keyAttribute.getName() + "'"); } } } @Override public void fetchEnd(DBCSession session, DBCResultSet resultSet) throws DBCException { } @Override public void close() { } } /** * Data statement */ static class DataStatementInfo { @NotNull final DBSManipulationType type; @NotNull final ResultSetRow row; @NotNull final DBSEntity entity; final List<DBDAttributeValue> keyAttributes = new ArrayList<>(); final List<DBDAttributeValue> updateAttributes = new ArrayList<>(); boolean executed = false; final Map<Integer, Object> updatedCells = new HashMap<>(); DataStatementInfo(@NotNull DBSManipulationType type, @NotNull ResultSetRow row, @NotNull DBSEntity entity) { this.type = type; this.row = row; this.entity = entity; } boolean needKeys() { for (DBDAttributeValue col : keyAttributes) { if (col.getAttribute().isAutoGenerated() && DBUtils.isNullValue(col.getValue())) { return true; } } return false; } } class RowDataReceiver implements DBDDataReceiver { private final DBDAttributeBinding[] curAttributes; private Object[] rowValues; public RowDataReceiver(DBDAttributeBinding[] curAttributes) { this.curAttributes = curAttributes; } @Override public void fetchStart(DBCSession session, DBCResultSet resultSet, long offset, long maxRows) throws DBCException { } @Override public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException { DBCResultSetMetaData rsMeta = resultSet.getMeta(); // Compare attributes with existing model attributes List<DBCAttributeMetaData> attributes = rsMeta.getAttributes(); if (attributes.size() != curAttributes.length) { log.debug("Wrong meta attributes count - can't refresh"); return; } for (int i = 0; i < curAttributes.length; i++) { if (!ResultSetUtils.equalAttributes(curAttributes[i].getMetaAttribute(), attributes.get(i))) { log.debug("Attribute '" + curAttributes[i].getMetaAttribute().getName() + "' doesn't match '" + attributes.get(i).getName() + "'"); return; } } rowValues = new Object[curAttributes.length]; for (int i = 0; i < curAttributes.length; i++) { final DBDAttributeBinding attr = curAttributes[i]; DBDValueHandler valueHandler = attr.getValueHandler(); Object attrValue = valueHandler.fetchValueObject(session, resultSet, attr, i); rowValues[i] = attrValue; } } @Override public void fetchEnd(DBCSession session, DBCResultSet resultSet) throws DBCException { } @Override public void close() { } } private class RowRefreshJob extends DataSourceJob { private DBSDataContainer dataContainer; private DBDRowIdentifier rowIdentifier; private List<ResultSetRow> rows; public RowRefreshJob(DBCExecutionContext context, DBSDataContainer dataContainer, DBDRowIdentifier rowIdentifier, List<ResultSetRow> rows) { super("Refresh rows", null, context); this.dataContainer = dataContainer; this.rowIdentifier = rowIdentifier; this.rows = new ArrayList<>(rows); } @Override protected IStatus run(DBRProgressMonitor monitor) { try { final Object[][] refreshValues = new Object[rows.size()][]; final DBDAttributeBinding[] curAttributes = viewer.getModel().getAttributes(); final AbstractExecutionSource executionSource = new AbstractExecutionSource(dataContainer, getExecutionContext(), this); List<DBDAttributeBinding> idAttributes = rowIdentifier.getAttributes(); if (idAttributes.isEmpty()) { return Status.OK_STATUS; } try (DBCSession session = getExecutionContext().openSession(monitor, DBCExecutionPurpose.UTIL, "Refresh row(s) after insert/update")) { for (int i = 0; i < rows.size(); i++) { ResultSetRow row = rows.get(i); List<DBDAttributeConstraint> constraints = new ArrayList<>(); boolean hasKey = true; for (DBDAttributeBinding keyAttr : idAttributes) { final Object keyValue = row.values[keyAttr.getOrdinalPosition()]; if (DBUtils.isNullValue(keyValue)) { hasKey = false; break; } final DBDAttributeConstraint constraint = new DBDAttributeConstraint(keyAttr); constraint.setOperator(DBCLogicalOperator.EQUALS); constraint.setValue(keyValue); constraints.add(constraint); } if (!hasKey) { // No key value for this row continue; } DBDDataFilter filter = new DBDDataFilter(constraints); RowDataReceiver dataReceiver = new RowDataReceiver(curAttributes); final DBCStatistics stats = dataContainer.readData(executionSource, session, dataReceiver, filter, 0, 0, DBSDataContainer.FLAG_NONE); refreshValues[i] = dataReceiver.rowValues; } } // Ok, now we have refreshed values. Let's update real model DBeaverUI.syncExec(new Runnable() { @Override public void run() { // Update only if metadata wasn't changed if (!viewer.getControl().isDisposed() && viewer.getModel().getAttributes() == curAttributes) { for (int i = 0; i < rows.size(); i++) { if (refreshValues[i] != null) { rows.get(i).values = refreshValues[i]; } } viewer.redrawData(false, true); } } }); } catch (Throwable ex) { log.warn("Error refreshing rows", ex); // Error happened during data refresh // Let's rollback if we are in transaction if (viewer.getPreferenceStore().getBoolean(ModelPreferences.QUERY_ROLLBACK_ON_ERROR)) { DBCTransactionManager txnManager = DBUtils.getTransactionManager(getExecutionContext()); try { if (txnManager != null && !txnManager.isAutoCommit()) { try (DBCSession session = getExecutionContext().openSession(monitor, DBCExecutionPurpose.UTIL, "Rollback after data refresh failure")) { txnManager.rollback(session, null); } } } catch (DBCException e) { log.warn("Error rolling back after data refresh failure", ex); } } } return Status.OK_STATUS; } } }