Java tutorial
/** * (c) Copyright 2013 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.schema; import java.io.Closeable; import java.io.IOException; import java.util.List; import java.util.Map; import com.google.common.base.Preconditions; import com.google.common.collect.Multimap; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.kiji.annotations.ApiAudience; import org.kiji.annotations.ApiStability; import org.kiji.schema.KijiTableReaderBuilder.OnDecoderCacheMiss; import org.kiji.schema.KijiTableReaderPool.PooledKijiTableReader; import org.kiji.schema.layout.CellSpec; import org.kiji.schema.layout.ColumnReaderSpec; /** * Maintains a pool of opened KijiTableReaders for reuse. * * <p> * KijiTableReaders retrieved from the pool should be closed as normal when no longer needed. * Closing a KijiTableReader from this pool will return it to the pool and make it available to * other users. * </p> * * <p> * KijiTableReaderPool instances retain the KijiTable associated with the KijiReaderFactory which * provides readers for the pool. Closing the pool will release the table. * </p> */ @ApiAudience.Public @ApiStability.Evolving public final class KijiTableReaderPool extends GenericObjectPool<PooledKijiTableReader> implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(KijiTableReaderPool.class); /** Builder for KijiTableReaderPool instances. */ public static final class Builder { /** * Create a new KijiTableReaderPool.Builder. * * @return a new KijiTableReaderPool.Builder. */ public static Builder create() { return new Builder(); } /** * Possible behaviors of {@link org.kiji.schema.KijiTableReaderPool#borrowObject()} when a pool * is full. */ public static enum WhenExhaustedAction { BLOCK, FAIL, GROW } /** * When build succeeds, the pool will retain the KijiTable associated with this * KijiReaderFactory. Closing the pool will release that table. */ private KijiReaderFactory mReaderFactory = null; private Map<KijiColumnName, CellSpec> mCellSpecOverrides = null; private Map<KijiColumnName, ColumnReaderSpec> mColumnReaderSpecOverrides = null; private Multimap<KijiColumnName, ColumnReaderSpec> mColumnReaderSpecAlternatives = null; private OnDecoderCacheMiss mOnDecoderCacheMiss = null; private Integer mMinIdle = null; private Integer mMaxIdle = null; private Integer mMaxActive = null; private Long mMinEvictableIdleTime = null; private Long mTimeBetweenEvictionRuns = null; private WhenExhaustedAction mWhenExhaustedAction = null; private Long mMaxWait = null; /** * Set the KijiReaderFactory which will provide readers for this pool. Obtainable via * {@link org.kiji.schema.KijiTable#getReaderFactory()}. This method is required to build a * KijiTableReaderPool. * * @param readerFactory KijiReaderFactory which will be used to provide readers for this pool. * @return this. */ public Builder withReaderFactory(final KijiReaderFactory readerFactory) { Preconditions.checkNotNull(readerFactory, "KijiReaderFactory may not be null."); Preconditions.checkState(null == mReaderFactory, "KijiReaderFactory is already set to: %s", mReaderFactory); mReaderFactory = readerFactory; return this; } /** * Set the CellSpec overrides to use to build readers for this pool. This field is optional and * defaults to no overrides. * * @param overrides CellSpec overrides which will be used to configure all readers served by * this pool. * @return this. */ public Builder withCellSpecOverrides(final Map<KijiColumnName, CellSpec> overrides) { Preconditions.checkNotNull(overrides, "CellSpec overrides may not be null."); Preconditions.checkState(null == mCellSpecOverrides, "CellSpec overrides are already set to: %s", mCellSpecOverrides); mCellSpecOverrides = overrides; return this; } /** * Set the minimum number of idle readers which the pool will attempt to maintain. If active + * idle would exceed max, this minimum will not be enforced. This field is optional and defaults * to {@link GenericObjectPool#DEFAULT_MIN_IDLE}. * * @param minIdle the minimum number of idle readers which the pool will attempt to maintain. * @return this. */ public Builder withMinIdle(final int minIdle) { Preconditions.checkArgument(0 <= minIdle, "Minimum idle count must be greater than or equal to 0."); Preconditions.checkState(null == mMinIdle, "Minimum idle count is already set to: %s", mMinIdle); mMinIdle = minIdle; return this; } /** * Set the maximum number of idle readers which may be maintained at a time. This field is * optional and defaults to {@link GenericObjectPool#DEFAULT_MAX_IDLE}. * * @param maxIdle maximum number of idle readers. * @return this. */ public Builder withMaxIdle(final int maxIdle) { Preconditions.checkArgument(0 <= maxIdle, "Maximum idle count must be greater than or equal to 0."); Preconditions.checkState(null == mMaxIdle, "Maximum idle count is already set to: %s", mMaxIdle); mMaxIdle = maxIdle; return this; } /** * Set the maximum number of simultaneously active readers. This field is optional and defaults * to {@link GenericObjectPool#DEFAULT_MAX_ACTIVE}. * * @param maxActive maximum number of active readers before * {@link org.kiji.schema.KijiTableReaderPool#borrowObject()} changes its behavior according * to {@link WhenExhaustedAction}. * @return this. */ public Builder withMaxActive(final int maxActive) { Preconditions.checkArgument(0 <= maxActive, "Maximum active count must be greater than or equal to 0."); Preconditions.checkState(null == mMaxActive, "Maximum active count is already set to: %s", mMaxActive); mMaxActive = maxActive; return this; } /** * Set the time in milliseconds after which a reader may be evicted for idleness. Readers are * not guaranteed to be evicted when this time has elapsed, but they may not be evicted before * it. This field is optional and defaults to * {@link GenericObjectPool#DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS}. * * @param minEvictableIdleTime time in milliseconds a reader must remain idle before it may be * evicted. * @return this. */ public Builder withMinEvictableIdleTime(final long minEvictableIdleTime) { Preconditions.checkArgument(0 <= minEvictableIdleTime, "Minimum idle time before eviction must be greater than or equal to 0."); Preconditions.checkState(null == mMinEvictableIdleTime, "Minimum idle time before eviction is already set to: %s", mMinEvictableIdleTime); mMinEvictableIdleTime = minEvictableIdleTime; return this; } /** * Set the time in milliseconds between automatic eviction of idle readers. This field is * optional and defaults to {@link GenericObjectPool#DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS}. * * @param timeBetweenEvictionRuns time in milliseconds to wait between evicting readers which * have been idle longer than the allowed duration. * @return this. */ public Builder withTimeBetweenEvictionRuns(final long timeBetweenEvictionRuns) { Preconditions.checkArgument(0 < timeBetweenEvictionRuns, "Time between eviction runs must be greater than 0."); Preconditions.checkState(null == mTimeBetweenEvictionRuns, "Time between eviction runs is already set to: %s", mTimeBetweenEvictionRuns); mTimeBetweenEvictionRuns = timeBetweenEvictionRuns; return this; } /** * Set the behavior of {@link org.kiji.schema.KijiTableReaderPool#borrowObject()} when the pool * is at maximum capacity. This field is optional and defaults to * {@link GenericObjectPool#DEFAULT_WHEN_EXHAUSTED_ACTION}. * * @param whenExhaustedAction behavior of * {@link org.kiji.schema.KijiTableReaderPool#borrowObject()} when the pool is at maximum * capacity. * @return this. */ public Builder withExhaustedAction(final WhenExhaustedAction whenExhaustedAction) { Preconditions.checkNotNull(whenExhaustedAction, "When exhausted action may not be null."); Preconditions.checkState(null == mWhenExhaustedAction, "When exhaused action is already set to: %s", mWhenExhaustedAction); mWhenExhaustedAction = whenExhaustedAction; return this; } /** * Set the maximum time (in milliseconds) to wait for an object to become available when using * {@link WhenExhaustedAction#BLOCK}. This field is optional and defaults to * {@link GenericObjectPool#DEFAULT_MAX_WAIT}. * * @param maxWait time in milliseconds to wait for an object to become available when borrowing. * @return this. */ public Builder withMaxWaitToBorrow(final long maxWait) { Preconditions.checkArgument(0 <= maxWait, "Max wait to borrow must be greater than or equal to 0."); Preconditions.checkState(null == mMaxWait, "Max wait to borrow is already set to: %s", mMaxWait); mMaxWait = maxWait; return this; } /** * Set the ColumnReaderSpec overrides which will be used as the default read behavior for * readers served from this pool. This field may not be set with CellSpec overrides. * * @param overrides mapping from column names to overriding ColumnReaderSpecs which will be the * default read behavior for readers served from this pool. * @return this. */ public Builder withColumnReaderSpecOverrides(final Map<KijiColumnName, ColumnReaderSpec> overrides) { Preconditions.checkNotNull(overrides, "ColumnReaderSpec overrides may not be null."); Preconditions.checkState(null == mColumnReaderSpecOverrides, "ColumnReaderSpec overrides are already set to: %s", mColumnReaderSpecOverrides); Preconditions.checkState(null == mCellSpecOverrides, "ColumnReaderSpec overrides are mutually" + " exclusive with CellSpec CellSpec overrides are already set to: %s", mCellSpecOverrides); mColumnReaderSpecOverrides = overrides; return this; } /** * Set the ColumnReaderSpec alternatives for which cell decoders will be available, but which * will not change the default behavior of read requests. Column name, ColumnReaderSpec pairs in * this list will not trigger failures when OnDecoderCacheMiss is set to FAIL. This field may * not be set with CellSpec overrides. * * @param alternatives mapping from column names to alternative ColumnReaderSpecs which will be * available from readers served by this pool. * @return this. */ public Builder withColumnReaderSpecAlternatives( final Multimap<KijiColumnName, ColumnReaderSpec> alternatives) { Preconditions.checkNotNull(alternatives, "ColumnReaderSpec alternatives may not be null."); Preconditions.checkState(null == mColumnReaderSpecAlternatives, "ColumnReaderSpec alternatives are already set to: %s", mColumnReaderSpecAlternatives); Preconditions.checkState(null == mCellSpecOverrides, "ColumnReaderSpec alternatives are " + "mutually exclusive with CellSpec overrides. CellSpec overrides are already set to: %s", mCellSpecOverrides); mColumnReaderSpecAlternatives = alternatives; return this; } /** * Set the behavior of readers served by this pool when they cannot find a cell decoder. This * field may not be set with CellSpec overrides. * * @param onDecoderCacheMissBehavior behavior of readers served by this pool when they cannot * find a cell decoder. * @return this. */ public Builder withOnDecoderCacheMissBehavior(final OnDecoderCacheMiss onDecoderCacheMissBehavior) { Preconditions.checkNotNull(onDecoderCacheMissBehavior, "OnDecoderCacheMiss behavior may not be null."); Preconditions.checkState(null == mOnDecoderCacheMiss, "OnDecoderCacheMiss behavior is already set to: %s", mOnDecoderCacheMiss); mOnDecoderCacheMiss = onDecoderCacheMissBehavior; return this; } /** * Get the KijiReaderFactory configured in this Builder, or null if none has been set. * * @return the KijiReaderFactory. */ public KijiReaderFactory getReaderFactory() { return mReaderFactory; } /** * Get the CellSpec overrides configured in this Builder, or null if none has been set. * @return the CellSpec overrides. */ public Map<KijiColumnName, CellSpec> getCellSpecOverrides() { return mCellSpecOverrides; } /** * Get the minimum number of idle readers before the reaper thread may begin garbage collection * configured in this Builder, or null if none has been set. * * @return the minimum number of idle readers before the reaper thread may begin garbage * collection. */ public Integer getMinIdle() { return mMinIdle; } /** * Get the maximum number of idle readers the pool will allow to exist at a time configured in * this Builder, or null if none has been set. * * @return the maximum number of idle readers the pool will allow to exist at a time. */ public Integer getMaxIdle() { return mMaxIdle; } /** * Get the maximum number of active (borrowed + idle) readers the pool will allow to exist at a * time configured in this Builder, or null if none has been set. * * @return the maximum number of active (borrowed + idle) readers the pool will allow to exist * at a time. */ public Integer getMaxActive() { return mMaxActive; } /** * Get the minimum idle time in milliseconds before a reader may be garbage collected configured * in this Builder, or null if none has been set. * * @return the minimum idle time in milliseconds before a reader may be garbage collected. */ public Long getMinEvictableIdleTime() { return mMinEvictableIdleTime; } /** * Get the time in milliseconds between eviction runs configured in this Builder, or null if * none has been set. * * @return the time in milliseconds between eviction runs. */ public Long getTimeBetweenEvictionRuns() { return mTimeBetweenEvictionRuns; } /** * Get the WhenExhaustedAction configured in this Builder, or null if none has been set. * * @return the WhenExhaustedAction. */ public WhenExhaustedAction getWhenExhaustedAction() { return mWhenExhaustedAction; } /** * Get the maximum time in milliseconds the borrowObject method should block before throwing an * exception when the WhenExhaustedAction is set to BLOCK configured in this Builder, or null if * none has been set. * * @return the maximum time in milliseconds the borrowObject method should block before throwing * an exception when the WhenExhaustedAction is set to BLOCK. */ public Long getMaxWait() { return mMaxWait; } /** * Get the ColumnReaderSpec overrides configured in this Builder, or null if none have been set. * * @return the ColumnReaderSpec overrides configured in this Builder, or null if none have been * set. */ public Map<KijiColumnName, ColumnReaderSpec> getColumnReaderSpecOverrides() { return mColumnReaderSpecOverrides; } /** * Get the ColumnReaderSpec alternatives configured in this Builder, or null if none have been * set. * * @return the ColumnReaderSpec alternatives configured in this Builder, or null if none have * been set. */ public Multimap<KijiColumnName, ColumnReaderSpec> getColumnReaderSpecAlternatives() { return mColumnReaderSpecAlternatives; } /** * Get the configured OnDecoderCacheMiss behavior from this Builder, or null if none has been * set. * * @return the configured OnDecoderCacheMiss behavior from this Builder, or null if none has * been set. */ public OnDecoderCacheMiss getOnDecoderCacheMiss() { return mOnDecoderCacheMiss; } /** * Build a new KijiTableReaderPool from the configured options. * * @return a new KijiTableReaderPool. * @throws IOException in case of an error getting the reader factory. */ public KijiTableReaderPool build() throws IOException { Preconditions.checkNotNull(mReaderFactory, "KijiReaderFactory may not be null."); final GenericObjectPool.Config config = new Config(); config.minIdle = (null != mMinIdle) ? mMinIdle : GenericObjectPool.DEFAULT_MIN_IDLE; config.maxIdle = (null != mMaxIdle) ? mMaxIdle : GenericObjectPool.DEFAULT_MAX_IDLE; config.maxActive = (null != mMaxActive) ? mMaxActive : GenericObjectPool.DEFAULT_MAX_ACTIVE; config.minEvictableIdleTimeMillis = (null != mMinEvictableIdleTime) ? mMinEvictableIdleTime : GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; config.timeBetweenEvictionRunsMillis = (null != mTimeBetweenEvictionRuns) ? mTimeBetweenEvictionRuns : GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; if (null != mWhenExhaustedAction) { switch (mWhenExhaustedAction) { case BLOCK: { config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK; break; } case FAIL: { config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_FAIL; break; } case GROW: { config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW; break; } default: throw new InternalKijiError( String.format("Unknown WhenExhaustedAction: %s", mWhenExhaustedAction)); } } else { config.whenExhaustedAction = GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION; } config.maxWait = (null != mMaxWait) ? mMaxWait : GenericObjectPool.DEFAULT_MAX_WAIT; // Set all unsupported options to default. config.lifo = GenericObjectPool.DEFAULT_LIFO; config.numTestsPerEvictionRun = GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; config.softMinEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS; config.testOnBorrow = GenericObjectPool.DEFAULT_TEST_ON_BORROW; config.testOnReturn = GenericObjectPool.DEFAULT_TEST_ON_RETURN; config.testWhileIdle = GenericObjectPool.DEFAULT_TEST_WHILE_IDLE; // Nothing that may fail may come after retain(). mReaderFactory.getTable().retain(); if (null == mCellSpecOverrides) { if (null == mColumnReaderSpecOverrides) { mColumnReaderSpecOverrides = KijiTableReaderBuilder.DEFAULT_READER_SPEC_OVERRIDES; } if (null == mColumnReaderSpecAlternatives) { mColumnReaderSpecAlternatives = KijiTableReaderBuilder.DEFAULT_READER_SPEC_ALTERNATIVES; } if (null == mOnDecoderCacheMiss) { mOnDecoderCacheMiss = KijiTableReaderBuilder.DEFAULT_CACHE_MISS; } return KijiTableReaderPool.create(mReaderFactory, mColumnReaderSpecOverrides, mColumnReaderSpecAlternatives, mOnDecoderCacheMiss, config); } else { return KijiTableReaderPool.create(mReaderFactory, mCellSpecOverrides, config); } } } /** Factory which provides readers for this pool. */ private final PooledKijiTableReaderFactory mFactory; /** * Initialize a new KijiTableReaderPool with the given reader factory and configuration. * * @param factory reader factory which will provide pooled readers for this pool. * @param config pool configuration which determines the pools behavior. */ private KijiTableReaderPool(final PooledKijiTableReaderFactory factory, final GenericObjectPool.Config config) { super(factory, config); factory.setPool(this); mFactory = factory; } /** * Create a new KijiTableReaderPool which uses the given reader factory to create reader objects. * * @param readerFactory KijiReaderFactory from which to get new reader instances. * @param overrides Optional CellSpec overrides with which to build the readers. * @param config Configuration which describes the behavior of the pool. * @return a new KijiTableReaderPool. */ public static KijiTableReaderPool create(final KijiReaderFactory readerFactory, final Map<KijiColumnName, CellSpec> overrides, final GenericObjectPool.Config config) { final PooledKijiTableReaderFactory factory = new PooledKijiTableReaderFactory(readerFactory, overrides); return new KijiTableReaderPool(factory, config); } /** * Create a new KijiTableReaderPool which uses the given reader factory to create reader objects. * * @param readerFactory KijiReaderFactory from which to get new reader instances. * @param overrides ColumnReaderSpec overrides with which to build the readers. * @param alternatives ColumnReaderSpec alternatives with which to build the readers. * @param onDecoderCacheMiss Behavior of the reader when a cell decoder cannot be found. * @param config Configuration which describes the behavior of the pool. * @return a new KijiTableReaderPool. */ public static KijiTableReaderPool create(final KijiReaderFactory readerFactory, final Map<KijiColumnName, ColumnReaderSpec> overrides, final Multimap<KijiColumnName, ColumnReaderSpec> alternatives, final OnDecoderCacheMiss onDecoderCacheMiss, final GenericObjectPool.Config config) { final PooledKijiTableReaderFactory factory = new PooledKijiTableReaderFactory(readerFactory, overrides, alternatives, onDecoderCacheMiss); return new KijiTableReaderPool(factory, config); } /** {@inheritDoc} */ @Override public void close() throws IOException { try { super.close(); } catch (IOException ioe) { throw ioe; } catch (Exception e) { throw new KijiIOException(e); } mFactory.mFactoryDelegate.getTable().release(); } /** * Factory for pooled KijiTableReaders. Users of KijiTableReaderPool should never need to see this * class. An instance of this class may only serve one KijiTableReaderPool at a time. */ public static final class PooledKijiTableReaderFactory implements PoolableObjectFactory<PooledKijiTableReader> { private final KijiReaderFactory mFactoryDelegate; private final Map<KijiColumnName, CellSpec> mCellSpecOverrides; private final Map<KijiColumnName, ColumnReaderSpec> mColumnReaderSpecOverrides; private final Multimap<KijiColumnName, ColumnReaderSpec> mColumnReaderSpecAlternatives; private final OnDecoderCacheMiss mOnDecoderCacheMiss; /** * This pool must be set using {@link #setPool(KijiTableReaderPool)} before any other operations * are attempted. */ private KijiTableReaderPool mPool; /** * Initialize a new PooledKijiTableReaderFactory wrapping the given KijiReaderFactory. * * @param factoryDelegate KijiReaderFactory to be wrapped by this factory. * @param overrides CellSpec overrides which will be used to construct all readers built by this * factory. */ public PooledKijiTableReaderFactory(final KijiReaderFactory factoryDelegate, final Map<KijiColumnName, CellSpec> overrides) { mFactoryDelegate = factoryDelegate; mCellSpecOverrides = overrides; mColumnReaderSpecOverrides = null; mColumnReaderSpecAlternatives = null; mOnDecoderCacheMiss = null; } /** * Initialize a new PooledKijiTableReaderFactory wrapping the given KijiReaderFactory. * * @param factoryDelegate KijiReaderFactory to be wrapped by this factory. * @param overrides ColumnReaderSpec overrides with which to build the readers. * @param alternatives ColumnReaderSpec alternatives with which to build the readers. * @param onDecoderCacheMiss Behavior of the reader when a cell decoder cannot be found. */ public PooledKijiTableReaderFactory(final KijiReaderFactory factoryDelegate, final Map<KijiColumnName, ColumnReaderSpec> overrides, final Multimap<KijiColumnName, ColumnReaderSpec> alternatives, final OnDecoderCacheMiss onDecoderCacheMiss) { mFactoryDelegate = factoryDelegate; mColumnReaderSpecOverrides = overrides; mColumnReaderSpecAlternatives = alternatives; mOnDecoderCacheMiss = onDecoderCacheMiss; mCellSpecOverrides = null; } /** * Set the pool this factory serves. This can only be set once in the lifetime of the factory. * Must be set before any other operations are called on the factory. * * @param pool KijiTableReaderPool which this factory will serve. */ private void setPool(final KijiTableReaderPool pool) { Preconditions.checkState(null == mPool, "Pool is already set to: %s", mPool); mPool = pool; } /** {@inheritDoc} */ @Override public PooledKijiTableReader makeObject() throws Exception { final KijiTableReader innerReader; if (null == mCellSpecOverrides) { innerReader = mFactoryDelegate.readerBuilder() .withColumnReaderSpecOverrides(mColumnReaderSpecOverrides) .withColumnReaderSpecAlternatives(mColumnReaderSpecAlternatives) .withOnDecoderCacheMiss(mOnDecoderCacheMiss).buildAndOpen(); } else { innerReader = mFactoryDelegate.openTableReader(mCellSpecOverrides); } return new PooledKijiTableReader(innerReader, mPool); } /** {@inheritDoc} */ @Override public void destroyObject(final PooledKijiTableReader obj) throws Exception { obj.mInnerReader.close(); } /** {@inheritDoc} */ @Override public boolean validateObject(final PooledKijiTableReader obj) { return obj.mAvailable; } /** {@inheritDoc} */ @Override public void activateObject(final PooledKijiTableReader obj) throws Exception { obj.mAvailable = false; } /** {@inheritDoc} */ @Override public void passivateObject(final PooledKijiTableReader obj) throws Exception { obj.mAvailable = true; } } /** * KijiTableReader implementation which can be served from a pool. {@link #close()} returns the * reader to the pool. */ public static final class PooledKijiTableReader implements KijiTableReader { private final KijiTableReader mInnerReader; private final KijiTableReaderPool mPool; /** True indicates that this reader is available to be served from the pool. */ private boolean mAvailable = true; /** * Initialize a new PooledKijiTableReader which uses the given inner reader to fulfill reads and * which is served from the given pool. * * @param innerReader KijiTableReader to which this pooled reader delegates for read operations. * @param pool KijiTableReaderPool from which this reader is served. */ public PooledKijiTableReader(final KijiTableReader innerReader, final KijiTableReaderPool pool) { mInnerReader = innerReader; mPool = pool; } /** {@inheritDoc} */ @Override public KijiRowData get(final EntityId entityId, final KijiDataRequest dataRequest) throws IOException { return mInnerReader.get(entityId, dataRequest); } /** {@inheritDoc} */ @Override public List<KijiRowData> bulkGet(final List<EntityId> entityIds, final KijiDataRequest dataRequest) throws IOException { return mInnerReader.bulkGet(entityIds, dataRequest); } /** {@inheritDoc} */ @Override public KijiRowScanner getScanner(final KijiDataRequest dataRequest) throws IOException { return mInnerReader.getScanner(dataRequest); } /** {@inheritDoc} */ @Override public KijiRowScanner getScanner(final KijiDataRequest dataRequest, final KijiScannerOptions scannerOptions) throws IOException { return mInnerReader.getScanner(dataRequest, scannerOptions); } /** {@inheritDoc} */ @Override public void close() throws IOException { try { mPool.returnObject(this); } catch (Exception e) { if (e instanceof IOException) { throw (IOException) e; } else { throw new RuntimeException(e); } } } } }