@SuppressWarnings("unchecked")
@Override
public <R> MultiResponse multi(MultiAction<R> multi) throws IOException {
checkOpen();
MultiResponse response = new MultiResponse();
for (Map.Entry<byte[], List<Action<R>>> e : multi.actions.entrySet()) {
byte[] regionName = e.getKey();
List<Action<R>> actionsForRegion = e.getValue();
// sort based on the row id - this helps in the case where we reach the
// end of a region, so that we don't have to try the rest of the
// actions in the list.
Collections.sort(actionsForRegion);
Row action;
List<Action<R>> mutations = new ArrayList<Action<R>>();
for (Action<R> a : actionsForRegion) {
action = a.getAction();
int originalIndex = a.getOriginalIndex();
try {
if (action instanceof Delete || action instanceof Put) {
mutations.add(a);
} else if (action instanceof Get) {
response.add(regionName, originalIndex,
get(regionName, (Get)action));
} else if (action instanceof Exec) {
ExecResult result = execCoprocessor(regionName, (Exec)action);
response.add(regionName, new Pair<Integer, Object>(
a.getOriginalIndex(), result.getValue()
));
} else if (action instanceof Increment) {
response.add(regionName, originalIndex,
increment(regionName, (Increment)action));
} else if (action instanceof Append) {
response.add(regionName, originalIndex,
append(regionName, (Append)action));
} else if (action instanceof RowMutations) {
mutateRow(regionName, (RowMutations)action);
response.add(regionName, originalIndex, new Result());
} else {
LOG.debug("Error: invalid Action, row must be a Get, Delete, " +
"Put, Exec, Increment, or Append.");
throw new DoNotRetryIOException("Invalid Action, row must be a " +
"Get, Delete, Put, Exec, Increment, or Append.");
}
} catch (IOException ex) {
response.add(regionName, originalIndex, ex);
}
}
// We do the puts with result.put so we can get the batching efficiency
// we so need. All this data munging doesn't seem great, but at least
// we arent copying bytes or anything.
if (!mutations.isEmpty()) {
try {
HRegion region = getRegion(regionName);
if (!region.getRegionInfo().isMetaTable()) {
this.cacheFlusher.reclaimMemStoreMemory();
}
List<Pair<Mutation,Integer>> mutationsWithLocks =
Lists.newArrayListWithCapacity(mutations.size());
for (Action<R> a : mutations) {
Mutation m = (Mutation) a.getAction();
Integer lock;
try {
lock = getLockFromId(m.getLockId());
} catch (UnknownRowLockException ex) {
response.add(regionName, a.getOriginalIndex(), ex);
continue;
}
mutationsWithLocks.add(new Pair<Mutation, Integer>(m, lock));
}
this.requestCount.addAndGet(mutations.size());
OperationStatus[] codes =
region.batchMutate(mutationsWithLocks.toArray(new Pair[]{}));
for( int i = 0 ; i < codes.length ; i++) {
OperationStatus code = codes[i];
Action<R> theAction = mutations.get(i);
Object result = null;
if (code.getOperationStatusCode() == OperationStatusCode.SUCCESS) {
result = new Result();
} else if (code.getOperationStatusCode()
== OperationStatusCode.SANITY_CHECK_FAILURE) {
// Don't send a FailedSanityCheckException as older clients will not know about
// that class being a subclass of DoNotRetryIOException
// and will retry mutations that will never succeed.
result = new DoNotRetryIOException(code.getExceptionMsg());
} else if (code.getOperationStatusCode() == OperationStatusCode.BAD_FAMILY) {
result = new NoSuchColumnFamilyException(code.getExceptionMsg());
}
// FAILURE && NOT_RUN becomes null, aka: need to run again.
response.add(regionName, theAction.getOriginalIndex(), result);
}
} catch (IOException ioe) {
// fail all the puts with the ioe in question.
for (Action<R> a: mutations) {
response.add(regionName, a.getOriginalIndex(), ioe);
}
}
}
}
return response;