/** * Copyright (c) <2013> <Radware Ltd.> and others. All rights reserved. * * This program and the accompanying materials are made available under the terms of the Eclipse Public License * v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * @author Gera Goft * @author Konstantin Pozdeev * @version 0.1 */ package org.opendaylight.defense4all.framework.core.impl; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import org.opendaylight.defense4all.framework.core.Asserter; import org.opendaylight.defense4all.framework.core.ExceptionControlApp; import org.opendaylight.defense4all.framework.core.ExceptionKey; import org.opendaylight.defense4all.framework.core.FrameworkMain; import org.opendaylight.defense4all.framework.core.HealthTracker; import org.opendaylight.defense4all.framework.core.Repo; import org.opendaylight.defense4all.framework.core.RepoCD; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import me.prettyprint.cassandra.serializers.BytesArraySerializer; import me.prettyprint.cassandra.serializers.ObjectSerializer; import me.prettyprint.cassandra.serializers.StringSerializer; import me.prettyprint.cassandra.service.ColumnSliceIterator; import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate; import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate; import me.prettyprint.hector.api.Serializer; import me.prettyprint.hector.api.beans.ColumnSlice; import me.prettyprint.hector.api.beans.HColumn; import me.prettyprint.hector.api.beans.OrderedRows; import me.prettyprint.hector.api.beans.Row; import me.prettyprint.hector.api.factory.HFactory; import me.prettyprint.hector.api.mutation.MutationResult; import me.prettyprint.hector.api.mutation.Mutator; import me.prettyprint.hector.api.query.ColumnQuery; import me.prettyprint.hector.api.query.QueryResult; import me.prettyprint.hector.api.query.RangeSlicesQuery; import me.prettyprint.hector.api.query.SliceQuery; //Repo name corresponds to FrameworkMain.RepoMajor.REPO_FACTORY + "_" + RepoFactoryImpl.RepoMinor.Repos public class RepoImpl<K> implements Repo<K> { private static final int MAX_COLUMNS_TO_RETURN = 1000; private static final int MAX_ROWS_CHUNK_TO_RETURN = 10000; // Cassandra may start timing out at 100000 rows at a time Logger log = LoggerFactory.getLogger(this.getClass()); protected RepoDescription repoDesc; protected RepoFactoryImpl rFactory = null; protected Serializer<K> keySerializer; protected ColumnFamilyTemplate<K, String> cfTemplate = null; protected Mutator<K> mutator = null; protected Mutator<K> opMutator = null; protected Serializer<String> stringSerializer = StringSerializer.get(); protected Serializer<byte[]> bytesArraySerializer = BytesArraySerializer.get(); protected Serializer<Object> objSerializer = ObjectSerializer.get(); RangeSlicesQuery<K, String, byte[]> tableQuery = null; RangeSlicesQuery<K, String, byte[]> keysQuery = null; SliceQuery<K,String, byte[]> rowQuery = null; ColumnQuery<K, String, byte[]> cellQuery = null; /** Constructor - Corresponds to inflation by Hector EntityManager * @param mRepoFactoryImpl * @param param_name param description * @throws ExceptionControlApp * @throws exception_type circumstances description */ public RepoImpl(FrameworkMainImpl frameworkMain, RepoDescription repoDesc) throws ExceptionControlApp { this(frameworkMain.repoFactoryImpl, repoDesc); } @SuppressWarnings("unchecked") protected RepoImpl(RepoFactoryImpl rf, RepoDescription rdesc) throws ExceptionControlApp { this.repoDesc = rdesc; @SuppressWarnings("rawtypes") Class<? extends Serializer> c; String keySerializerClassName = rdesc.keySerializerClassName; String invalidClassMsg = "Invalid key serializer class name provided - \"" + keySerializerClassName + "\". "; try { c = Class.forName(keySerializerClassName).asSubclass(Serializer.class); keySerializer = c.newInstance(); } catch (Throwable e) { throw new IllegalArgumentException(invalidClassMsg, e); } this.rFactory = rf; try { cfTemplate = new ThriftColumnFamilyTemplate<K, String>(rFactory.ctrlAppsKS, repoDesc.repoName, keySerializer, stringSerializer); mutator = HFactory.createMutator(rFactory.ctrlAppsKS, keySerializer); opMutator = HFactory.createMutator(rFactory.ctrlAppsKS, keySerializer); tableQuery = HFactory.createRangeSlicesQuery(rFactory.ctrlAppsKS, keySerializer, stringSerializer, bytesArraySerializer); tableQuery.setColumnFamily(repoDesc.repoName).setRange(null, null, false, MAX_COLUMNS_TO_RETURN).setRowCount(MAX_ROWS_CHUNK_TO_RETURN); keysQuery = HFactory.createRangeSlicesQuery(rFactory.ctrlAppsKS, keySerializer, stringSerializer, bytesArraySerializer); keysQuery.setColumnFamily(repoDesc.repoName).setRange(null, null, false, 1).setRowCount(MAX_ROWS_CHUNK_TO_RETURN); // keysQuery = HFactory.createRangeSlicesQuery(rFactory.ctrlAppsKS, keySerializer, stringSerializer, bytesArraySerializer); // keysQuery.setColumnFamily(repoDesc.repoName).setKeys(null, null).setReturnKeysOnly(); rowQuery = HFactory.createSliceQuery(rFactory.ctrlAppsKS, keySerializer, stringSerializer, bytesArraySerializer); rowQuery.setColumnFamily(repoDesc.repoName); cellQuery = HFactory.createColumnQuery(rFactory.ctrlAppsKS, keySerializer, stringSerializer, bytesArraySerializer); cellQuery.setColumnFamily(repoDesc.repoName); } catch (Throwable e) { log.error("Failed to initialize Repo." + e.getLocalizedMessage()); rf.fMainImpl.frImpl.logRecord(FrameworkMain.FR_FRAMEWORK_FAILURE, "Internal Database Failure"); throw new ExceptionControlApp("Failed to initialize Repo.", e); } } /** Pre-shutdown cleanup */ public void finit() { try { applyUpdateBatch(); } catch (Throwable e) { log.error("Failed to apply update batch." + e.getLocalizedMessage()); rFactory.fMainImpl.frImpl.logRecord(FrameworkMain.FR_FRAMEWORK_FAILURE, "Internal Database Failure"); } } /** Factory reset */ public void factoryReset() { // delete repo and remove any externally visible effects, like external notifications } /** Adds column description to the table (no need to add the key column). * @param param_name param description * @throws ExceptionControlApp * @throws exception_type circumstances description */ public void addColumnDescription(RepoCD columnDescription) throws ExceptionControlApp { if (columnDescription == null) return; try { cfTemplate.addColumn(columnDescription.columnName, columnDescription.columnValueSerializer); } catch (Exception e) { log.error("Failed to add column to CFTemplate." + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to add column to CFTemplate.", e); } } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ public void applyUpdateBatch() throws ExceptionControlApp { applyUpdateBatch(mutator); } protected void applyUpdateBatch(Mutator<K> mutator) throws ExceptionControlApp { /* Cassandra may be chocked by too many concurrent requests so throttle it and retry a few times. */ MutationResult mutationResult = null; for(int i=0;i<3;i++) { try { mutationResult = mutator.execute(); // Has no affect in case of immediate updates break; } catch (Throwable e) {} try { Thread.sleep(500); } catch (InterruptedException e) {} } if(mutationResult == null) { log.error("Failed to flush to Cassandra mutator cached repo updates."); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to flush to Cassandra mutator cached repo updates."); } log.trace("mutator execution time in usecs:"+mutationResult.getExecutionTimeMicro()+", host:"+mutationResult.getHostUsed()); } /** Get Keys of all non empty (deleted) rows. * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ public List<K> getKeys() throws ExceptionControlApp { /* We do not use keys-only as it may return keys for empty (deleted) rows. Instead we retrieve at least * one column, just to make sure it is not a deleted row. */ ArrayList<K> resultKeys = new ArrayList<K>(); K key; /* Cassandra may be chocked by too many concurrent requests so throttle it and retry a few times. */ QueryResult<OrderedRows<K, String, byte[]>> queryResult = retryExecute(keysQuery, 3); if(queryResult == null) return null; OrderedRows<K, String, byte[]> rows = queryResult.get(); if(rows == null) return resultKeys; Iterator<Row<K, String, byte[]>> rowsIter = rows.iterator(); if(rowsIter == null) return resultKeys; // Return empty list. Could also return null. Row<K, String, byte[]> row; while (rowsIter.hasNext()) { row = rowsIter.next(); key = row.getKey(); if(!(row.getColumnSlice().getColumns().isEmpty())) resultKeys.add(key); } return resultKeys; } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws exception_type circumstances description */ public Hashtable<K, Hashtable<String, Object>> getTable() { K lastKey = null; Hashtable<K, Hashtable<String, Object>> resultTable = new Hashtable<K, Hashtable<String, Object>>(); Hashtable<String, Object> resultRow; Object resultCellValue; String resultColumnName; QueryResult<OrderedRows<K, String, byte[]>> chunkQueryResult; OrderedRows<K, String, byte[]> rows; Iterator<Row<K, String, byte[]>> rowsIter; Row<K, String, byte[]> row; Iterator<HColumn<String, byte[]>> columnIter; HColumn<String, byte[]> hColumn; Serializer<?> valueSerializer; byte [] bytesValue; while (true) { // Iterate overall chunks tableQuery.setKeys(lastKey, null); /* Cassandra may be chocked by too many concurrent requests so throttle it and retry a few times. */ chunkQueryResult = retryExecute(tableQuery, 3); if(chunkQueryResult == null) return null; rows = chunkQueryResult.get(); if(rows == null) break; rowsIter = rows.iterator(); if(rowsIter == null) break; if (lastKey != null && rowsIter.hasNext()) rowsIter.next(); // skip this first one, since it is the same as the last one from previous time we executed while (rowsIter.hasNext()) { // Iterate over a single row chunk row = rowsIter.next(); if(row == null) { log.error("Received null row. Skipping."); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); continue; } lastKey = row.getKey(); columnIter = row.getColumnSlice().getColumns().iterator(); if(columnIter == null) continue; resultRow = new Hashtable<String, Object>(); try { while(columnIter.hasNext()) { // Iterate over a single row in a row chunk hColumn = columnIter.next(); resultColumnName = hColumn.getName(); bytesValue = hColumn.getValue(); valueSerializer = getValueSerializer(resultColumnName, objSerializer); resultCellValue = valueSerializer.fromBytes(bytesValue); resultRow.put(resultColumnName, resultCellValue); } } catch (Throwable e) { log.error("Failed to retrieve row " + lastKey + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); continue; } if(resultRow.size() > 0) // Not a cassandra tombestone row resultTable.put(lastKey, resultRow); } if (rows.getCount() < MAX_ROWS_CHUNK_TO_RETURN) break; // Got the last chunk of rows } return resultTable; } /** If the entry of the specified key already exists in Repo, then any cell in the passed in entry with value different * than the value of the latest recorded cell overrides the currently stored value. * @param param_name param description. * @return return entry id (key column value) * @throws ExceptionControlApp * @throws ExceptionKey If null or empty non-integer key is passed */ @SuppressWarnings("unchecked") public void setRow(K key, Hashtable<String, Object> cells) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Asserter.assertNonNullObjectParam(cells, "cells", log); Iterator<String> columnNamesIterator = cells.keySet().iterator(); String columnName = ""; Object cellValue = ""; Serializer<Object> valueSerializer; HColumn<String, Object> hColumn; Mutator<K> mutor = repoDesc.immediateFlush ? mutator : opMutator; try { while(columnNamesIterator.hasNext()) { columnName = columnNamesIterator.next(); cellValue = cells.get(columnName); valueSerializer = (Serializer<Object>) cfTemplate.getValueSerializer(columnName); if (valueSerializer == null) // Not a registered column. Will simply leave it as bytes array (byte[]). valueSerializer = objSerializer; hColumn = HFactory.createColumn(columnName, cellValue, stringSerializer, valueSerializer); mutor.addInsertion(key, repoDesc.repoName, hColumn); } } catch (Throwable e) { if(columnName == null) columnName = ""; if(cellValue == null) cellValue = ""; log.error("Failed to populate a column to mutator "+columnName+":"+cellValue+". "+e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); if(repoDesc.immediateFlush) // Mutator only contains updates from this setRow, so we can safely cancel them mutor.discardPendingMutations(); // Otherwise the mutator may contain updates from multiple methods, so we prefer inconsistency of this row // to discarding other updates. } if(repoDesc.immediateFlush) applyUpdateBatch(mutor); } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ public void deleteRow(K key) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Mutator<K> mutor = repoDesc.immediateFlush ? mutator : opMutator; try { mutor.addDeletion(key, repoDesc.repoName); } catch (Throwable e) { log.error("Failed to delete row " + key + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to delete row " + key + ". ", e); } if(repoDesc.immediateFlush) applyUpdateBatch(mutor); } /** This method deletes all rows. * @param param_name param description * @return return the value of the specified entry cell * @throws exception_type circumstances description */ @Override public void truncate() { for(int i=0;i<3;i++) { try { rFactory.ctrlAppsCluster.truncate(rFactory.dbName, repoDesc.repoName); break; } catch (Throwable e) { log.error("Failed to truncate repo"+repoDesc.repoName+". "+e.getLocalizedMessage()); rFactory.fMainImpl.frImpl.logRecord(FrameworkMain.FR_FRAMEWORK_FAILURE,"Internal Database Failure"); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); } } } /** ##description ### * @param param_name param description * @return return the all entry cells (including the empty ones). * @throws ExceptionControlApp * @throws exception_type circumstances description */ public Hashtable<String, Object> getRow(K key) throws ExceptionControlApp { rowQuery.setKey(key).setRange(null, null, false, MAX_COLUMNS_TO_RETURN); /* Cassandra may be chocked by too many concurrent requests so throttle it and retry a few times. */ QueryResult<ColumnSlice<String, byte[]>> result = null; for(int i=0;i<3;i++) { try { result = rowQuery.execute(); break; } catch (Throwable e) { log.error("Failed to execute row query for " + key + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); } try { Thread.sleep(500); } catch (InterruptedException e) {} } if(result == null) return null; List<HColumn<String, byte[]>> hColumns = result.get().getColumns(); if(hColumns.size() == 0) return null; // Cassandra returns empty row (no columns) for non existing keys. Iterator<HColumn<String, byte[]>> iter = hColumns.iterator(); Hashtable<String, Object> cells = new Hashtable<String, Object>(); HColumn<String, byte[]> hColumn; String columnName; Serializer<?> valueSerializer; byte [] bytesValue; Object value; try { while(iter.hasNext()) { hColumn = iter.next(); columnName = hColumn.getName(); bytesValue = hColumn.getValue(); valueSerializer = getValueSerializer(columnName, objSerializer); value = valueSerializer.fromBytes(bytesValue); cells.put(columnName, value); } } catch (Throwable e) { log.error("Failed to execute row query for " + key + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to populate received row " + key + ". ", e); } return cells; } /** ##description ### * @param param_name param description * @return return ordered list of record column names. * @throws exception_type circumstances description */ public List<String> getOrderedColumns(K key, int number, boolean reverse) { return getOrderedColumns ( key, number, reverse, null); } public List<String> getOrderedColumns(K key, int number, boolean reverse, List<?> filterList) { rowQuery.setKey(key); ColumnSliceIterator<K, String, byte[]> iterator; if (! reverse) iterator = new ColumnSliceIterator<K, String, byte[]>(rowQuery, null, "\uFFFF", false); else iterator = new ColumnSliceIterator<K, String, byte[]>(rowQuery, "\uFFFF", "", true); List<String> listColumns = new ArrayList<String>(); while (iterator.hasNext() ) { HColumn<String, byte[]> hColumn = null; try { hColumn = iterator.next(); if (filterList != null ) { byte[] bytesValue = hColumn.getValue(); Serializer<?> valueSerializer = getValueSerializer((String) hColumn.getName(), objSerializer); Object value = valueSerializer.fromBytes(bytesValue); if ( ! filterList.contains(value)) continue; } listColumns.add(hColumn.getName()); if (number != 0 && listColumns.size() >= number ) break; } catch (Throwable e) { String colStr = (hColumn == null) ? "" : hColumn.getName(); if(colStr == null) colStr = ""; log.error("Failed to retrieve cell " + colStr + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); } } return listColumns; } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ public Object getCellValue(K key, String columnName) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Asserter.assertNonEmptyStringParam(columnName, "columnName", log); cellQuery.setKey(key).setName(columnName); /* Cassandra may be chocked by too many concurrent requests so throttle it and retry a few times. */ QueryResult<HColumn<String, byte[]>> queryResult = null; boolean reported = false; for(int i=0;i<3;i++) { try { queryResult = cellQuery.execute(); break; } catch (Throwable e) { if(!reported) { log.info("Failed to execute cell query for "+ key +": " + columnName + e.getLocalizedMessage() + "could be because the column has not yet been set."); reported = true; } } try { Thread.sleep(500); } catch (InterruptedException e) {} } if(queryResult == null) return null; Object value; HColumn<String, byte[]> hColumn; try { hColumn = queryResult.get(); if(hColumn == null) return null; byte[] bytesValue = hColumn.getValue(); if(bytesValue == null) return null; Serializer<?> valueSerializer = getValueSerializer(columnName, objSerializer); value = valueSerializer.fromBytes(bytesValue); } catch (Throwable e) { log.error("Failed to inflate cell " + key +" : " + columnName + ". " + e.toString()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to inflate cell " + key +": " + columnName + ". ", e); } return value; } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ @Override public boolean hasCell(K key, String columnName) { if(key == null || columnName == null) return false; QueryResult<HColumn<String, byte[]>> queryResult = null; try { cellQuery.setKey(key).setName(columnName); queryResult = cellQuery.execute(); HColumn<String, byte[]> hColumn = queryResult.get(); if(hColumn == null) return false; byte[] bytesValue = hColumn.getValue(); if(bytesValue == null) return false; return true; } catch (Throwable e) { return false; // Exception is expected when cell does not exists. } } /** If the cell of the specified key and column name has value different than the value of the * latest recorded cell then the passed in value overrides the currently stored value. * @param param_name param description. * @return return entry id (key column value) * @throws ExceptionControlApp * @throws ExceptionKey If null or empty non-integer key is passed */ @SuppressWarnings("unchecked") public void setCell(K key, String columnName, Object cellValue) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Asserter.assertNonNullObjectParam(columnName, "columnName", log); Mutator<K> mutor = repoDesc.immediateFlush ? mutator : opMutator; Serializer<Object> valueSerializer; HColumn<String, Object> hColumn; try { valueSerializer = (Serializer<Object>) cfTemplate.getValueSerializer(columnName); if (valueSerializer == null) // Not a registered column. Will simply leave it as bytes array (byte[]). valueSerializer = objSerializer; hColumn = HFactory.createColumn(columnName, cellValue, stringSerializer, valueSerializer); mutor.addInsertion(key, repoDesc.repoName, hColumn); } catch (Exception e) { log.error("Failed to populate a column to mutator "+columnName +":"+cellValue+". "+e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); if(repoDesc.immediateFlush) // Mutator only contains updates from this setRow, so we can safely cancel them mutor.discardPendingMutations(); // Otherwise the mutator may contain updates from multiple methods, so we prefer inconsistency of this row // to discarding other updates. } if(repoDesc.immediateFlush) applyUpdateBatch(mutor); } protected QueryResult<OrderedRows<K,String,byte[]>> retryExecute(RangeSlicesQuery<K,String,byte[]> query, int retries) { QueryResult<OrderedRows<K,String,byte[]>> queryResult = null; for(int i=0;i<3;i++) { try { queryResult = query.execute(); // Has no affect in case of immediate updates break; } catch (Throwable e) { log.error("Failed to execute slice query. " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); } try { Thread.sleep(500); } catch (InterruptedException e) {} } return queryResult; } protected Serializer<?> getValueSerializer(String columnName, Serializer<?> defaultSerializer) { Asserter.assertNonEmptyStringParam(columnName, "columnName", log); Asserter.assertNonNullObjectParam(defaultSerializer, "defaultSerializer", log); Serializer<?> valueSerializer = cfTemplate.getValueSerializer(columnName); if (valueSerializer == null) // Not a registered column. use default serializer. return defaultSerializer; return valueSerializer; } /** ##description ### * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ public void deleteCell(K key, String columnName) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Asserter.assertNonEmptyStringParam(columnName, "columnName", log); Mutator<K> mutor = repoDesc.immediateFlush ? mutator : opMutator; try { mutor.addDeletion(key, repoDesc.repoName, columnName, stringSerializer); } catch (Throwable e) { log.error("Failed to delete row " + key + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to delete row " + key + ". ", e); } if(repoDesc.immediateFlush) applyUpdateBatch(mutor); } /** Delete all fields from the record. * @param param_name param description * @return return the value of the specified entry cell * @throws ExceptionControlApp * @throws exception_type circumstances description */ @Override public void deleteCells(K key, List<String> cellKeys) throws ExceptionControlApp { Asserter.assertNonNullObjectParam(key, "key", log); Asserter.assertNonNullObjectParam(cellKeys, "cellKeys", log); Mutator<K> mutor = repoDesc.immediateFlush ? mutator : opMutator; for(String cellKey : cellKeys) { try { mutor.addDeletion(key, repoDesc.repoName, cellKey, stringSerializer); } catch (Throwable e) { log.error("Failed to delete cells in row " + key + ". " + e.getLocalizedMessage()); rFactory.fMainImpl.healthTrackerImpl.reportHealthIssue(HealthTracker.MODERATE_HEALTH_ISSUE); throw new ExceptionControlApp("Failed to delete cells in row " + key + ". ", e); } } if(repoDesc.immediateFlush) applyUpdateBatch(mutor); } }