Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.accumulo.core.client.impl; import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.AccumuloException; import org.apache.accumulo.core.client.AccumuloSecurityException; import org.apache.accumulo.core.client.ConditionalWriter; import org.apache.accumulo.core.client.ConditionalWriterConfig; import org.apache.accumulo.core.client.Durability; import org.apache.accumulo.core.client.Instance; import org.apache.accumulo.core.client.TableDeletedException; import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.TableOfflineException; import org.apache.accumulo.core.client.TimedOutException; import org.apache.accumulo.core.client.impl.TabletLocator.TabletServerMutations; import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Condition; import org.apache.accumulo.core.data.ConditionalMutation; import org.apache.accumulo.core.data.impl.KeyExtent; import org.apache.accumulo.core.data.thrift.TCMResult; import org.apache.accumulo.core.data.thrift.TCMStatus; import org.apache.accumulo.core.data.thrift.TCondition; import org.apache.accumulo.core.data.thrift.TConditionalMutation; import org.apache.accumulo.core.data.thrift.TConditionalSession; import org.apache.accumulo.core.data.thrift.TKeyExtent; import org.apache.accumulo.core.data.thrift.TMutation; import org.apache.accumulo.core.master.state.tables.TableState; import org.apache.accumulo.core.rpc.ThriftUtil; import org.apache.accumulo.core.security.Authorizations; import org.apache.accumulo.core.security.ColumnVisibility; import org.apache.accumulo.core.security.VisibilityEvaluator; import org.apache.accumulo.core.security.VisibilityParseException; import org.apache.accumulo.core.tabletserver.thrift.NoSuchScanIDException; import org.apache.accumulo.core.tabletserver.thrift.TabletClientService; import org.apache.accumulo.core.trace.Trace; import org.apache.accumulo.core.trace.Tracer; import org.apache.accumulo.core.trace.thrift.TInfo; import org.apache.accumulo.core.util.BadArgumentException; import org.apache.accumulo.core.util.ByteBufferUtil; import org.apache.accumulo.core.util.NamingThreadFactory; import org.apache.accumulo.core.zookeeper.ZooUtil; import org.apache.accumulo.fate.util.LoggingRunnable; import org.apache.accumulo.fate.zookeeper.ZooCacheFactory; import org.apache.accumulo.fate.zookeeper.ZooLock; import org.apache.accumulo.fate.zookeeper.ZooUtil.LockID; import org.apache.commons.collections.map.LRUMap; import org.apache.commons.lang.mutable.MutableLong; import org.apache.hadoop.io.Text; import org.apache.thrift.TApplicationException; import org.apache.thrift.TException; import org.apache.thrift.TServiceClient; import org.apache.thrift.transport.TTransportException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.net.HostAndPort; class ConditionalWriterImpl implements ConditionalWriter { private static ThreadPoolExecutor cleanupThreadPool = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); static { cleanupThreadPool.allowCoreThreadTimeOut(true); } private static final Logger log = LoggerFactory.getLogger(ConditionalWriterImpl.class); private static final int MAX_SLEEP = 30000; private Authorizations auths; private VisibilityEvaluator ve; @SuppressWarnings("unchecked") private Map<Text, Boolean> cache = Collections.synchronizedMap(new LRUMap(1000)); private final ClientContext context; private TabletLocator locator; private final String tableId; private long timeout; private final Durability durability; private final String classLoaderContext; private static class ServerQueue { BlockingQueue<TabletServerMutations<QCMutation>> queue = new LinkedBlockingQueue<TabletServerMutations<QCMutation>>(); boolean taskQueued = false; } private Map<String, ServerQueue> serverQueues; private DelayQueue<QCMutation> failedMutations = new DelayQueue<QCMutation>(); private ScheduledThreadPoolExecutor threadPool; private class RQIterator implements Iterator<Result> { private BlockingQueue<Result> rq; private int count; public RQIterator(BlockingQueue<Result> resultQueue, int count) { this.rq = resultQueue; this.count = count; } @Override public boolean hasNext() { return count > 0; } @Override public Result next() { if (count <= 0) throw new NoSuchElementException(); try { Result result = rq.poll(1, TimeUnit.SECONDS); while (result == null) { if (threadPool.isShutdown()) { throw new NoSuchElementException("ConditionalWriter closed"); } result = rq.poll(1, TimeUnit.SECONDS); } count--; return result; } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void remove() { throw new UnsupportedOperationException(); } } private static class QCMutation extends ConditionalMutation implements Delayed { private BlockingQueue<Result> resultQueue; private long resetTime; private long delay = 50; private long entryTime; QCMutation(ConditionalMutation cm, BlockingQueue<Result> resultQueue, long entryTime) { super(cm); this.resultQueue = resultQueue; this.entryTime = entryTime; } @Override public int compareTo(Delayed o) { QCMutation oqcm = (QCMutation) o; return Long.compare(resetTime, oqcm.resetTime); } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object o) { if (o instanceof QCMutation) return compareTo((QCMutation) o) == 0; return false; } @Override public long getDelay(TimeUnit unit) { return unit.convert(delay - (System.currentTimeMillis() - resetTime), TimeUnit.MILLISECONDS); } void resetDelay() { delay = Math.min(delay * 2, MAX_SLEEP); resetTime = System.currentTimeMillis(); } void queueResult(Result result) { resultQueue.add(result); } } private ServerQueue getServerQueue(String location) { ServerQueue serverQueue; synchronized (serverQueues) { serverQueue = serverQueues.get(location); if (serverQueue == null) { serverQueue = new ServerQueue(); serverQueues.put(location, serverQueue); } } return serverQueue; } private class CleanupTask implements Runnable { private List<SessionID> sessions; CleanupTask(List<SessionID> activeSessions) { this.sessions = activeSessions; } @Override public void run() { TabletClientService.Iface client = null; for (SessionID sid : sessions) { if (!sid.isActive()) continue; TInfo tinfo = Tracer.traceInfo(); try { client = getClient(sid.location); client.closeConditionalUpdate(tinfo, sid.sessionID); } catch (Exception e) { } finally { ThriftUtil.returnClient((TServiceClient) client); } } } } private void queueRetry(List<QCMutation> mutations, HostAndPort server) { if (timeout < Long.MAX_VALUE) { long time = System.currentTimeMillis(); ArrayList<QCMutation> mutations2 = new ArrayList<ConditionalWriterImpl.QCMutation>(mutations.size()); for (QCMutation qcm : mutations) { qcm.resetDelay(); if (time + qcm.getDelay(TimeUnit.MILLISECONDS) > qcm.entryTime + timeout) { TimedOutException toe; if (server != null) toe = new TimedOutException(Collections.singleton(server.toString())); else toe = new TimedOutException("Conditional mutation timed out"); qcm.queueResult(new Result(toe, qcm, (null == server ? null : server.toString()))); } else { mutations2.add(qcm); } } if (mutations2.size() > 0) failedMutations.addAll(mutations2); } else { for (QCMutation qcm : mutations) qcm.resetDelay(); failedMutations.addAll(mutations); } } private void queue(List<QCMutation> mutations) { List<QCMutation> failures = new ArrayList<QCMutation>(); Map<String, TabletServerMutations<QCMutation>> binnedMutations = new HashMap<String, TabletLocator.TabletServerMutations<QCMutation>>(); try { locator.binMutations(context, mutations, binnedMutations, failures); if (failures.size() == mutations.size()) if (!Tables.exists(context.getInstance(), tableId)) throw new TableDeletedException(tableId); else if (Tables.getTableState(context.getInstance(), tableId) == TableState.OFFLINE) throw new TableOfflineException(context.getInstance(), tableId); } catch (Exception e) { for (QCMutation qcm : mutations) qcm.queueResult(new Result(e, qcm, null)); // do not want to queue anything that was put in before binMutations() failed failures.clear(); binnedMutations.clear(); } if (failures.size() > 0) queueRetry(failures, null); for (Entry<String, TabletServerMutations<QCMutation>> entry : binnedMutations.entrySet()) { queue(entry.getKey(), entry.getValue()); } } private void queue(String location, TabletServerMutations<QCMutation> mutations) { ServerQueue serverQueue = getServerQueue(location); synchronized (serverQueue) { serverQueue.queue.add(mutations); // never execute more than one task per server if (!serverQueue.taskQueued) { threadPool.execute(new LoggingRunnable(log, Trace.wrap(new SendTask(location)))); serverQueue.taskQueued = true; } } } private void reschedule(SendTask task) { ServerQueue serverQueue = getServerQueue(task.location); // just finished processing work for this server, could reschedule if it has more work or immediately process the work // this code reschedules the the server for processing later... there may be other queues with // more data that need to be processed... also it will give the current server time to build // up more data... the thinking is that rescheduling instead or processing immediately will result // in bigger batches and less RPC overhead synchronized (serverQueue) { if (serverQueue.queue.size() > 0) threadPool.execute(new LoggingRunnable(log, Trace.wrap(task))); else serverQueue.taskQueued = false; } } private TabletServerMutations<QCMutation> dequeue(String location) { BlockingQueue<TabletServerMutations<QCMutation>> queue = getServerQueue(location).queue; ArrayList<TabletServerMutations<QCMutation>> mutations = new ArrayList<TabletLocator.TabletServerMutations<QCMutation>>(); queue.drainTo(mutations); if (mutations.size() == 0) return null; if (mutations.size() == 1) { return mutations.get(0); } else { // merge multiple request to a single tablet server TabletServerMutations<QCMutation> tsm = mutations.get(0); for (int i = 1; i < mutations.size(); i++) { for (Entry<KeyExtent, List<QCMutation>> entry : mutations.get(i).getMutations().entrySet()) { List<QCMutation> list = tsm.getMutations().get(entry.getKey()); if (list == null) { list = new ArrayList<QCMutation>(); tsm.getMutations().put(entry.getKey(), list); } list.addAll(entry.getValue()); } } return tsm; } } ConditionalWriterImpl(ClientContext context, String tableId, ConditionalWriterConfig config) { this.context = context; this.auths = config.getAuthorizations(); this.ve = new VisibilityEvaluator(config.getAuthorizations()); this.threadPool = new ScheduledThreadPoolExecutor(config.getMaxWriteThreads(), new NamingThreadFactory(this.getClass().getSimpleName())); this.locator = new SyncingTabletLocator(context, tableId); this.serverQueues = new HashMap<String, ServerQueue>(); this.tableId = tableId; this.timeout = config.getTimeout(TimeUnit.MILLISECONDS); this.durability = config.getDurability(); this.classLoaderContext = config.getClassLoaderContext(); Runnable failureHandler = new Runnable() { @Override public void run() { List<QCMutation> mutations = new ArrayList<QCMutation>(); failedMutations.drainTo(mutations); if (mutations.size() > 0) queue(mutations); } }; failureHandler = new LoggingRunnable(log, failureHandler); threadPool.scheduleAtFixedRate(failureHandler, 250, 250, TimeUnit.MILLISECONDS); } @Override public Iterator<Result> write(Iterator<ConditionalMutation> mutations) { BlockingQueue<Result> resultQueue = new LinkedBlockingQueue<Result>(); List<QCMutation> mutationList = new ArrayList<QCMutation>(); int count = 0; long entryTime = System.currentTimeMillis(); mloop: while (mutations.hasNext()) { ConditionalMutation mut = mutations.next(); count++; if (mut.getConditions().size() == 0) throw new IllegalArgumentException( "ConditionalMutation had no conditions " + new String(mut.getRow(), UTF_8)); for (Condition cond : mut.getConditions()) { if (!isVisible(cond.getVisibility())) { resultQueue.add(new Result(Status.INVISIBLE_VISIBILITY, mut, null)); continue mloop; } } // copy the mutations so that even if caller changes it, it will not matter mutationList.add(new QCMutation(mut, resultQueue, entryTime)); } queue(mutationList); return new RQIterator(resultQueue, count); } private class SendTask implements Runnable { String location; public SendTask(String location) { this.location = location; } @Override public void run() { try { TabletServerMutations<QCMutation> mutations = dequeue(location); if (mutations != null) sendToServer(HostAndPort.fromString(location), mutations); } finally { reschedule(this); } } } private static class CMK { QCMutation cm; KeyExtent ke; public CMK(KeyExtent ke, QCMutation cm) { this.ke = ke; this.cm = cm; } } private static class SessionID { HostAndPort location; String lockId; long sessionID; boolean reserved; long lastAccessTime; long ttl; boolean isActive() { return System.currentTimeMillis() - lastAccessTime < ttl * .95; } } private HashMap<HostAndPort, SessionID> cachedSessionIDs = new HashMap<HostAndPort, SessionID>(); private SessionID reserveSessionID(HostAndPort location, TabletClientService.Iface client, TInfo tinfo) throws ThriftSecurityException, TException { // avoid cost of repeatedly making RPC to create sessions, reuse sessions synchronized (cachedSessionIDs) { SessionID sid = cachedSessionIDs.get(location); if (sid != null) { if (sid.reserved) throw new IllegalStateException(); if (!sid.isActive()) { cachedSessionIDs.remove(location); } else { sid.reserved = true; return sid; } } } TConditionalSession tcs = client.startConditionalUpdate(tinfo, context.rpcCreds(), ByteBufferUtil.toByteBuffers(auths.getAuthorizations()), tableId, DurabilityImpl.toThrift(durability), this.classLoaderContext); synchronized (cachedSessionIDs) { SessionID sid = new SessionID(); sid.reserved = true; sid.sessionID = tcs.sessionId; sid.lockId = tcs.tserverLock; sid.ttl = tcs.ttl; sid.location = location; if (cachedSessionIDs.put(location, sid) != null) throw new IllegalStateException(); return sid; } } private void invalidateSessionID(HostAndPort location) { synchronized (cachedSessionIDs) { cachedSessionIDs.remove(location); } } private void unreserveSessionID(HostAndPort location) { synchronized (cachedSessionIDs) { SessionID sid = cachedSessionIDs.get(location); if (sid != null) { if (!sid.reserved) throw new IllegalStateException(); sid.reserved = false; sid.lastAccessTime = System.currentTimeMillis(); } } } List<SessionID> getActiveSessions() { ArrayList<SessionID> activeSessions = new ArrayList<SessionID>(); for (SessionID sid : cachedSessionIDs.values()) if (sid.isActive()) activeSessions.add(sid); return activeSessions; } private TabletClientService.Iface getClient(HostAndPort location) throws TTransportException { TabletClientService.Iface client; if (timeout < context.getClientTimeoutInMillis()) client = ThriftUtil.getTServerClient(location, context, timeout); else client = ThriftUtil.getTServerClient(location, context); return client; } private void sendToServer(HostAndPort location, TabletServerMutations<QCMutation> mutations) { TabletClientService.Iface client = null; TInfo tinfo = Tracer.traceInfo(); Map<Long, CMK> cmidToCm = new HashMap<Long, CMK>(); MutableLong cmid = new MutableLong(0); SessionID sessionId = null; try { Map<TKeyExtent, List<TConditionalMutation>> tmutations = new HashMap<TKeyExtent, List<TConditionalMutation>>(); CompressedIterators compressedIters = new CompressedIterators(); convertMutations(mutations, cmidToCm, cmid, tmutations, compressedIters); // getClient() call must come after converMutations in case it throws a TException client = getClient(location); List<TCMResult> tresults = null; while (tresults == null) { try { sessionId = reserveSessionID(location, client, tinfo); tresults = client.conditionalUpdate(tinfo, sessionId.sessionID, tmutations, compressedIters.getSymbolTable()); } catch (NoSuchScanIDException nssie) { sessionId = null; invalidateSessionID(location); } } HashSet<KeyExtent> extentsToInvalidate = new HashSet<KeyExtent>(); ArrayList<QCMutation> ignored = new ArrayList<QCMutation>(); for (TCMResult tcmResult : tresults) { if (tcmResult.status == TCMStatus.IGNORED) { CMK cmk = cmidToCm.get(tcmResult.cmid); ignored.add(cmk.cm); extentsToInvalidate.add(cmk.ke); } else { QCMutation qcm = cmidToCm.get(tcmResult.cmid).cm; qcm.queueResult(new Result(fromThrift(tcmResult.status), qcm, location.toString())); } } for (KeyExtent ke : extentsToInvalidate) { locator.invalidateCache(ke); } queueRetry(ignored, location); } catch (ThriftSecurityException tse) { AccumuloSecurityException ase = new AccumuloSecurityException(context.getCredentials().getPrincipal(), tse.getCode(), Tables.getPrintableTableInfoFromId(context.getInstance(), tableId), tse); queueException(location, cmidToCm, ase); } catch (TTransportException e) { locator.invalidateCache(context.getInstance(), location.toString()); invalidateSession(location, mutations, cmidToCm, sessionId); } catch (TApplicationException tae) { queueException(location, cmidToCm, new AccumuloServerException(location.toString(), tae)); } catch (TException e) { locator.invalidateCache(context.getInstance(), location.toString()); invalidateSession(location, mutations, cmidToCm, sessionId); } catch (Exception e) { queueException(location, cmidToCm, e); } finally { if (sessionId != null) unreserveSessionID(location); ThriftUtil.returnClient((TServiceClient) client); } } private void queueRetry(Map<Long, CMK> cmidToCm, HostAndPort location) { ArrayList<QCMutation> ignored = new ArrayList<QCMutation>(); for (CMK cmk : cmidToCm.values()) ignored.add(cmk.cm); queueRetry(ignored, location); } private void queueException(HostAndPort location, Map<Long, CMK> cmidToCm, Exception e) { for (CMK cmk : cmidToCm.values()) cmk.cm.queueResult(new Result(e, cmk.cm, location.toString())); } private void invalidateSession(HostAndPort location, TabletServerMutations<QCMutation> mutations, Map<Long, CMK> cmidToCm, SessionID sessionId) { if (sessionId == null) { queueRetry(cmidToCm, location); } else { try { invalidateSession(sessionId, location); for (CMK cmk : cmidToCm.values()) cmk.cm.queueResult(new Result(Status.UNKNOWN, cmk.cm, location.toString())); } catch (Exception e2) { queueException(location, cmidToCm, e2); } } } /** * The purpose of this code is to ensure that a conditional mutation will not execute when its status is unknown. This allows a user to read the row when the * status is unknown and not have to worry about the tserver applying the mutation after the scan. * * <p> * If a conditional mutation is taking a long time to process, then this method will wait for it to finish... unless this exceeds timeout. */ private void invalidateSession(SessionID sessionId, HostAndPort location) throws AccumuloException, AccumuloSecurityException, TableNotFoundException { long sleepTime = 50; long startTime = System.currentTimeMillis(); Instance instance = context.getInstance(); LockID lid = new LockID(ZooUtil.getRoot(instance) + Constants.ZTSERVERS, sessionId.lockId); ZooCacheFactory zcf = new ZooCacheFactory(); while (true) { if (!ZooLock.isLockHeld( zcf.getZooCache(instance.getZooKeepers(), instance.getZooKeepersSessionTimeOut()), lid)) { // ACCUMULO-1152 added a tserver lock check to the tablet location cache, so this invalidation prevents future attempts to contact the // tserver even its gone zombie and is still running w/o a lock locator.invalidateCache(context.getInstance(), location.toString()); return; } try { // if the mutation is currently processing, this method will block until its done or times out invalidateSession(sessionId.sessionID, location); return; } catch (TApplicationException tae) { throw new AccumuloServerException(location.toString(), tae); } catch (TException e) { locator.invalidateCache(context.getInstance(), location.toString()); } if ((System.currentTimeMillis() - startTime) + sleepTime > timeout) throw new TimedOutException(Collections.singleton(location.toString())); sleepUninterruptibly(sleepTime, TimeUnit.MILLISECONDS); sleepTime = Math.min(2 * sleepTime, MAX_SLEEP); } } private void invalidateSession(long sessionId, HostAndPort location) throws TException { TabletClientService.Iface client = null; TInfo tinfo = Tracer.traceInfo(); try { client = getClient(location); client.invalidateConditionalUpdate(tinfo, sessionId); } finally { ThriftUtil.returnClient((TServiceClient) client); } } private Status fromThrift(TCMStatus status) { switch (status) { case ACCEPTED: return Status.ACCEPTED; case REJECTED: return Status.REJECTED; case VIOLATED: return Status.VIOLATED; default: throw new IllegalArgumentException(status.toString()); } } private void convertMutations(TabletServerMutations<QCMutation> mutations, Map<Long, CMK> cmidToCm, MutableLong cmid, Map<TKeyExtent, List<TConditionalMutation>> tmutations, CompressedIterators compressedIters) { for (Entry<KeyExtent, List<QCMutation>> entry : mutations.getMutations().entrySet()) { TKeyExtent tke = entry.getKey().toThrift(); ArrayList<TConditionalMutation> tcondMutaions = new ArrayList<TConditionalMutation>(); List<QCMutation> condMutations = entry.getValue(); for (QCMutation cm : condMutations) { TMutation tm = cm.toThrift(); List<TCondition> conditions = convertConditions(cm, compressedIters); cmidToCm.put(cmid.longValue(), new CMK(entry.getKey(), cm)); TConditionalMutation tcm = new TConditionalMutation(conditions, tm, cmid.longValue()); cmid.increment(); tcondMutaions.add(tcm); } tmutations.put(tke, tcondMutaions); } } static class ConditionComparator implements Comparator<Condition> { private static final Long MAX = Long.valueOf(Long.MAX_VALUE); @Override public int compare(Condition c1, Condition c2) { int comp = c1.getFamily().compareTo(c2.getFamily()); if (comp == 0) { comp = c1.getQualifier().compareTo(c2.getQualifier()); if (comp == 0) { comp = c1.getVisibility().compareTo(c2.getVisibility()); if (comp == 0) { Long l1 = c1.getTimestamp(); Long l2 = c2.getTimestamp(); if (l1 == null) { l1 = MAX; } if (l2 == null) { l2 = MAX; } comp = l2.compareTo(l1); } } } return comp; } } private static final ConditionComparator CONDITION_COMPARATOR = new ConditionComparator(); private List<TCondition> convertConditions(ConditionalMutation cm, CompressedIterators compressedIters) { List<TCondition> conditions = new ArrayList<TCondition>(cm.getConditions().size()); // sort conditions inorder to get better lookup performance. Sort on client side so tserver does not have to do it. Condition[] ca = cm.getConditions().toArray(new Condition[cm.getConditions().size()]); Arrays.sort(ca, CONDITION_COMPARATOR); for (Condition cond : ca) { long ts = 0; boolean hasTs = false; if (cond.getTimestamp() != null) { ts = cond.getTimestamp(); hasTs = true; } ByteBuffer iters = compressedIters.compress(cond.getIterators()); TCondition tc = new TCondition(ByteBufferUtil.toByteBuffers(cond.getFamily()), ByteBufferUtil.toByteBuffers(cond.getQualifier()), ByteBufferUtil.toByteBuffers(cond.getVisibility()), ts, hasTs, ByteBufferUtil.toByteBuffers(cond.getValue()), iters); conditions.add(tc); } return conditions; } private boolean isVisible(ByteSequence cv) { Text testVis = new Text(cv.toArray()); if (testVis.getLength() == 0) return true; Boolean b = cache.get(testVis); if (b != null) return b; try { Boolean bb = ve.evaluate(new ColumnVisibility(testVis)); cache.put(new Text(testVis), bb); return bb; } catch (VisibilityParseException e) { return false; } catch (BadArgumentException e) { return false; } } @Override public Result write(ConditionalMutation mutation) { return write(Collections.singleton(mutation).iterator()).next(); } @Override public void close() { threadPool.shutdownNow(); cleanupThreadPool.execute(new CleanupTask(getActiveSessions())); } }