// Copyright 2017 JanusGraph Authors // // 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.janusgraph.diskstorage.cassandra.astyanax; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.netflix.astyanax.AstyanaxContext; import com.netflix.astyanax.Cluster; import com.netflix.astyanax.ColumnListMutation; import com.netflix.astyanax.Keyspace; import com.netflix.astyanax.MutationBatch; import com.netflix.astyanax.connectionpool.Host; import com.netflix.astyanax.connectionpool.NodeDiscoveryType; import com.netflix.astyanax.connectionpool.RetryBackoffStrategy; import com.netflix.astyanax.connectionpool.SSLConnectionContext; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; import com.netflix.astyanax.connectionpool.impl.ConnectionPoolConfigurationImpl; import com.netflix.astyanax.connectionpool.impl.ConnectionPoolType; import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor; import com.netflix.astyanax.connectionpool.impl.ExponentialRetryBackoffStrategy; import com.netflix.astyanax.connectionpool.impl.SimpleAuthenticationCredentials; import com.netflix.astyanax.ddl.ColumnFamilyDefinition; import com.netflix.astyanax.ddl.KeyspaceDefinition; import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; import com.netflix.astyanax.model.ColumnFamily; import com.netflix.astyanax.retry.RetryPolicy; import com.netflix.astyanax.thrift.ThriftFamilyFactory; import org.janusgraph.diskstorage.BackendException; import org.janusgraph.diskstorage.Entry; import org.janusgraph.diskstorage.EntryMetaData; import org.janusgraph.diskstorage.PermanentBackendException; import org.janusgraph.diskstorage.StaticBuffer; import org.janusgraph.diskstorage.StoreMetaData; import org.janusgraph.diskstorage.TemporaryBackendException; import org.janusgraph.diskstorage.cassandra.AbstractCassandraStoreManager; import org.janusgraph.diskstorage.configuration.ConfigNamespace; import org.janusgraph.diskstorage.configuration.ConfigOption; import org.janusgraph.diskstorage.configuration.Configuration; import org.janusgraph.diskstorage.keycolumnvalue.KCVMutation; import org.janusgraph.diskstorage.keycolumnvalue.KeyRange; import org.janusgraph.diskstorage.keycolumnvalue.StoreTransaction; import org.janusgraph.graphdb.configuration.PreInitializeConfigOptions; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.utils.FBUtilities; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.janusgraph.diskstorage.cassandra.CassandraTransaction.getTx; @PreInitializeConfigOptions public class AstyanaxStoreManager extends AbstractCassandraStoreManager { private static final Logger log = LoggerFactory.getLogger(AstyanaxStoreManager.class); //################### ASTYANAX SPECIFIC CONFIGURATION OPTIONS ###################### public static final ConfigNamespace ASTYANAX_NS = new ConfigNamespace(CASSANDRA_NS, "astyanax", "Astyanax-specific Cassandra options"); /** * Default name for the Cassandra cluster * <p/> */ public static final ConfigOption<String> CLUSTER_NAME = new ConfigOption<String>(ASTYANAX_NS, "cluster-name", "Default name for the Cassandra cluster", ConfigOption.Type.MASKABLE, "JanusGraph Cluster"); /** * Maximum pooled connections per host. * <p/> */ public static final ConfigOption<Integer> MAX_CONNECTIONS_PER_HOST = new ConfigOption<Integer>(ASTYANAX_NS, "max-connections-per-host", "Maximum pooled connections per host", ConfigOption.Type.MASKABLE, 32); /** * Maximum open connections allowed in the pool (counting all hosts). * <p/> */ public static final ConfigOption<Integer> MAX_CONNECTIONS = new ConfigOption<Integer>(ASTYANAX_NS, "max-connections", "Maximum open connections allowed in the pool (counting all hosts)", ConfigOption.Type.MASKABLE, -1); /** * Maximum number of operations allowed per connection before the connection is closed. * <p/> */ public static final ConfigOption<Integer> MAX_OPERATIONS_PER_CONNECTION = new ConfigOption<Integer>(ASTYANAX_NS, "max-operations-per-connection", "Maximum number of operations allowed per connection before the connection is closed", ConfigOption.Type.MASKABLE, 100 * 1000); /** * Maximum pooled "cluster" connections per host. * <p/> * These connections are mostly idle and only used for DDL operations * (like creating keyspaces). JanusGraph doesn't need many of these connections * in ordinary operation. */ public static final ConfigOption<Integer> MAX_CLUSTER_CONNECTIONS_PER_HOST = new ConfigOption<Integer>(ASTYANAX_NS, "max-cluster-connections-per-host", "Maximum pooled \"cluster\" connections per host", ConfigOption.Type.MASKABLE, 3); /** * How Astyanax discovers Cassandra cluster nodes. This must be one of the * values of the Astyanax NodeDiscoveryType enum. * <p/> */ public static final ConfigOption<String> NODE_DISCOVERY_TYPE = new ConfigOption<String>(ASTYANAX_NS, "node-discovery-type", "How Astyanax discovers Cassandra cluster nodes", ConfigOption.Type.MASKABLE, "RING_DESCRIBE"); /** * Astyanax specific host supplier useful only when discovery type set to DISCOVERY_SERVICE or TOKEN_AWARE. * Excepts fully qualified class name which extends google.common.base.Supplier<List<Host>>. */ public static final ConfigOption<String> HOST_SUPPLIER = new ConfigOption<String>(ASTYANAX_NS, "host-supplier", "Host supplier to use when discovery type is set to DISCOVERY_SERVICE or TOKEN_AWARE", ConfigOption.Type.MASKABLE, String.class); /** * Astyanax's connection pooler implementation. This must be one of the * values of the Astyanax ConnectionPoolType enum. * <p/> */ public static final ConfigOption<String> CONNECTION_POOL_TYPE = new ConfigOption<String>(ASTYANAX_NS, "connection-pool-type", "Astyanax's connection pooler implementation", ConfigOption.Type.MASKABLE, "TOKEN_AWARE"); /** * In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar * but are used for distinct purposes. RetryPolicy is for retrying failed * operations. RetryBackoffStrategy is for retrying attempts to talk to * uncommunicative hosts. This config option controls RetryPolicy. */ public static final ConfigOption<String> RETRY_POLICY = new ConfigOption<String>(ASTYANAX_NS, "retry-policy", "Astyanax's retry policy implementation with configuration parameters", ConfigOption.Type.MASKABLE, "com.netflix.astyanax.retry.BoundedExponentialBackoff,100,25000,8"); /** * If non-null, this must be the fully-qualified classname (i.e. the * complete package name, a dot, and then the class name) of an * implementation of Astyanax's RetryBackoffStrategy interface. This string * may be followed by a sequence of integers, separated from the full * classname and from each other by commas; in this case, the integers are * cast to native Java ints and passed to the class constructor as * arguments. Here's an example setting that would instantiate an Astyanax * FixedRetryBackoffStrategy with an delay interval of 1s and suspend time * of 5s: * <p/> * <code> * com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000 * </code> * <p/> * If null, then Astyanax uses its default strategy, which is an * ExponentialRetryBackoffStrategy instance. The instance parameters take * Astyanax's built-in default values, which can be overridden via the * following config keys: * <ul> * <li>{@link #RETRY_DELAY_SLICE}</li> * <li>{@link #RETRY_MAX_DELAY_SLICE}</li> * <li>{@link #RETRY_SUSPEND_WINDOW}</li> * </ul> * <p/> * In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar * but are used for distinct purposes. RetryPolicy is for retrying failed * operations. RetryBackoffStrategy is for retrying attempts to talk to * uncommunicative hosts. This config option controls RetryBackoffStrategy. */ public static final ConfigOption<String> RETRY_BACKOFF_STRATEGY = new ConfigOption<String>(ASTYANAX_NS, "retry-backoff-strategy", "Astyanax's retry backoff strategy with configuration parameters", ConfigOption.Type.MASKABLE, "com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000"); /** * Controls the retryDelaySlice parameter on Astyanax * ConnectionPoolConfigurationImpl objects, which is in turn used by * ExponentialRetryBackoffStrategy. See the code for * {@link ConnectionPoolConfigurationImpl}, * {@link ExponentialRetryBackoffStrategy}, and the javadoc for * {@link #RETRY_BACKOFF_STRATEGY} for more information. * <p/> * This parameter is not meaningful for and has no effect on * FixedRetryBackoffStrategy. */ public static final ConfigOption<Integer> RETRY_DELAY_SLICE = new ConfigOption<Integer>(ASTYANAX_NS, "retry-delay-slice", "Astyanax's connection pool \"retryDelaySlice\" parameter", ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_DELAY_SLICE); /** * Controls the retryMaxDelaySlice parameter on Astyanax * ConnectionPoolConfigurationImpl objects, which is in turn used by * ExponentialRetryBackoffStrategy. See the code for * {@link ConnectionPoolConfigurationImpl}, * {@link ExponentialRetryBackoffStrategy}, and the javadoc for * {@link #RETRY_BACKOFF_STRATEGY} for more information. * <p/> * This parameter is not meaningful for and has no effect on * FixedRetryBackoffStrategy. */ public static final ConfigOption<Integer> RETRY_MAX_DELAY_SLICE = new ConfigOption<Integer>(ASTYANAX_NS, "retry-max-delay-slice", "Astyanax's connection pool \"retryMaxDelaySlice\" parameter", ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_MAX_DELAY_SLICE); /** * Controls the retrySuspendWindow parameter on Astyanax * ConnectionPoolConfigurationImpl objects, which is in turn used by * ExponentialRetryBackoffStrategy. See the code for * {@link ConnectionPoolConfigurationImpl}, * {@link ExponentialRetryBackoffStrategy}, and the javadoc for * {@link #RETRY_BACKOFF_STRATEGY} for more information. * <p/> * This parameter is not meaningful for and has no effect on * FixedRetryBackoffStrategy. */ public static final ConfigOption<Integer> RETRY_SUSPEND_WINDOW = new ConfigOption<Integer>(ASTYANAX_NS, "retry-suspend-window", "Astyanax's connection pool \"retryMaxDelaySlice\" parameter", ConfigOption.Type.MASKABLE, ConnectionPoolConfigurationImpl.DEFAULT_RETRY_SUSPEND_WINDOW); /** * Controls the frame size of thrift sockets created by Astyanax. */ public static final ConfigOption<Integer> THRIFT_FRAME_SIZE = new ConfigOption<Integer>(ASTYANAX_NS, "frame-size", "The thrift frame size in mega bytes", ConfigOption.Type.MASKABLE, 15); public static final ConfigOption<String> LOCAL_DATACENTER = new ConfigOption<String>(ASTYANAX_NS, "local-datacenter", "The name of the local or closest Cassandra datacenter. When set and not whitespace, " + "this value will be passed into ConnectionPoolConfigurationImpl.setLocalDatacenter. " + "When unset or set to whitespace, setLocalDatacenter will not be invoked.", /* It's between either LOCAL or MASKABLE. MASKABLE could be useful for cases where all the JanusGraph instances are closest to the same Cassandra DC. */ ConfigOption.Type.MASKABLE, String.class); private final String clusterName; private final AstyanaxContext<Keyspace> keyspaceContext; private final AstyanaxContext<Cluster> clusterContext; private final RetryPolicy retryPolicy; private final int retryDelaySlice; private final int retryMaxDelaySlice; private final int retrySuspendWindow; private final RetryBackoffStrategy retryBackoffStrategy; private final String localDatacenter; private final Map<String, AstyanaxKeyColumnValueStore> openStores; public AstyanaxStoreManager(Configuration config) throws BackendException { super(config); this.clusterName = config.get(CLUSTER_NAME); retryDelaySlice = config.get(RETRY_DELAY_SLICE); retryMaxDelaySlice = config.get(RETRY_MAX_DELAY_SLICE); retrySuspendWindow = config.get(RETRY_SUSPEND_WINDOW); retryBackoffStrategy = getRetryBackoffStrategy(config.get(RETRY_BACKOFF_STRATEGY)); retryPolicy = getRetryPolicy(config.get(RETRY_POLICY)); localDatacenter = config.has(LOCAL_DATACENTER) ? config.get(LOCAL_DATACENTER) : ""; final int maxConnsPerHost = config.get(MAX_CONNECTIONS_PER_HOST); final int maxClusterConnsPerHost = config.get(MAX_CLUSTER_CONNECTIONS_PER_HOST); this.clusterContext = createCluster(getContextBuilder(config, maxClusterConnsPerHost, "Cluster")); ensureKeyspaceExists(clusterContext.getClient()); this.keyspaceContext = getContextBuilder(config, maxConnsPerHost, "Keyspace").buildKeyspace(ThriftFamilyFactory.getInstance()); this.keyspaceContext.start(); openStores = new HashMap<String, AstyanaxKeyColumnValueStore>(8); } @Override public Deployment getDeployment() { return Deployment.REMOTE; // TODO } @Override public IPartitioner getCassandraPartitioner() throws BackendException { Cluster cl = clusterContext.getClient(); try { return FBUtilities.newPartitioner(cl.describePartitioner()); } catch (ConnectionException e) { throw new TemporaryBackendException(e); } catch (ConfigurationException e) { throw new PermanentBackendException(e); } } @Override public String toString() { return "astyanax" + super.toString(); } @Override public void close() { // Shutdown the Astyanax contexts openStores.clear(); keyspaceContext.shutdown(); clusterContext.shutdown(); } @Override public synchronized AstyanaxKeyColumnValueStore openDatabase(String name, StoreMetaData.Container metaData) throws BackendException { if (openStores.containsKey(name)) return openStores.get(name); else { ensureColumnFamilyExists(name); AstyanaxKeyColumnValueStore store = new AstyanaxKeyColumnValueStore(name, keyspaceContext.getClient(), this, retryPolicy); openStores.put(name, store); return store; } } @Override public void mutateMany(Map<String, Map<StaticBuffer, KCVMutation>> batch, StoreTransaction txh) throws BackendException { MutationBatch m = keyspaceContext.getClient().prepareMutationBatch().withAtomicBatch(atomicBatch) .setConsistencyLevel(getTx(txh).getWriteConsistencyLevel().getAstyanax()) .withRetryPolicy(retryPolicy.duplicate()); final MaskedTimestamp commitTime = new MaskedTimestamp(txh); for (Map.Entry<String, Map<StaticBuffer, KCVMutation>> batchentry : batch.entrySet()) { String storeName = batchentry.getKey(); Preconditions.checkArgument(openStores.containsKey(storeName), "Store cannot be found: " + storeName); ColumnFamily<ByteBuffer, ByteBuffer> columnFamily = openStores.get(storeName).getColumnFamily(); Map<StaticBuffer, KCVMutation> mutations = batchentry.getValue(); for (Map.Entry<StaticBuffer, KCVMutation> ent : mutations.entrySet()) { // The CLMs for additions and deletions are separated because // Astyanax's operation timestamp cannot be set on a per-delete // or per-addition basis. KCVMutation janusgraphMutation = ent.getValue(); ByteBuffer key = ent.getKey().asByteBuffer(); if (janusgraphMutation.hasDeletions()) { ColumnListMutation<ByteBuffer> dels = m.withRow(columnFamily, key); dels.setTimestamp(commitTime.getDeletionTime(times)); for (StaticBuffer b : janusgraphMutation.getDeletions()) dels.deleteColumn(b.as(StaticBuffer.BB_FACTORY)); } if (janusgraphMutation.hasAdditions()) { ColumnListMutation<ByteBuffer> upds = m.withRow(columnFamily, key); upds.setTimestamp(commitTime.getAdditionTime(times)); for (Entry e : janusgraphMutation.getAdditions()) { Integer ttl = (Integer) e.getMetaData().get(EntryMetaData.TTL); if (null != ttl && ttl > 0) { upds.putColumn(e.getColumnAs(StaticBuffer.BB_FACTORY), e.getValueAs(StaticBuffer.BB_FACTORY), ttl); } else { upds.putColumn(e.getColumnAs(StaticBuffer.BB_FACTORY), e.getValueAs(StaticBuffer.BB_FACTORY)); } } } } } try { m.execute(); } catch (ConnectionException e) { throw new TemporaryBackendException(e); } sleepAfterWrite(txh, commitTime); } @Override public List<KeyRange> getLocalKeyPartition() throws BackendException { throw new UnsupportedOperationException(); } @Override public void clearStorage() throws BackendException { try { Cluster cluster = clusterContext.getClient(); Keyspace ks = cluster.getKeyspace(keySpaceName); // Not a big deal if Keyspace doesn't not exist (dropped manually by user or tests). // This is called on per test setup basis to make sure that previous test cleaned // everything up, so first invocation would always fail as Keyspace doesn't yet exist. if (ks == null) return; for (ColumnFamilyDefinition cf : cluster.describeKeyspace(keySpaceName).getColumnFamilyList()) { ks.truncateColumnFamily(new ColumnFamily<Object, Object>(cf.getName(), null, null)); } } catch (ConnectionException e) { throw new PermanentBackendException(e); } } private void ensureColumnFamilyExists(String name) throws BackendException { ensureColumnFamilyExists(name, "org.apache.cassandra.db.marshal.BytesType"); } private void ensureColumnFamilyExists(String name, String comparator) throws BackendException { Cluster cl = clusterContext.getClient(); try { KeyspaceDefinition ksDef = cl.describeKeyspace(keySpaceName); boolean found = false; if (null != ksDef) { for (ColumnFamilyDefinition cfDef : ksDef.getColumnFamilyList()) { found |= cfDef.getName().equals(name); } } if (!found) { ColumnFamilyDefinition cfDef = cl.makeColumnFamilyDefinition() .setName(name) .setKeyspace(keySpaceName) .setComparatorType(comparator); ImmutableMap.Builder<String, String> compressionOptions = new ImmutableMap.Builder<String, String>(); if (storageConfig.has(COMPACTION_STRATEGY)) { cfDef.setCompactionStrategy(storageConfig.get(COMPACTION_STRATEGY)); } if (!compactionOptions.isEmpty()) { cfDef.setCompactionStrategyOptions(compactionOptions); } if (compressionEnabled) { compressionOptions.put("sstable_compression", compressionClass) .put("chunk_length_kb", Integer.toString(compressionChunkSizeKB)); } cl.addColumnFamily(cfDef.setCompressionOptions(compressionOptions.build())); } } catch (ConnectionException e) { throw new TemporaryBackendException(e); } } private static AstyanaxContext<Cluster> createCluster(AstyanaxContext.Builder cb) { AstyanaxContext<Cluster> clusterCtx = cb.buildCluster(ThriftFamilyFactory.getInstance()); clusterCtx.start(); return clusterCtx; } private AstyanaxContext.Builder getContextBuilder(Configuration config, int maxConnsPerHost, String usedFor) { final ConnectionPoolType poolType = ConnectionPoolType.valueOf(config.get(CONNECTION_POOL_TYPE)); final NodeDiscoveryType discType = NodeDiscoveryType.valueOf(config.get(NODE_DISCOVERY_TYPE)); final int maxConnections = config.get(MAX_CONNECTIONS); final int maxOperationsPerConnection = config.get(MAX_OPERATIONS_PER_CONNECTION); final int connectionTimeout = (int) connectionTimeoutMS.toMillis(); ConnectionPoolConfigurationImpl cpool = new ConnectionPoolConfigurationImpl(usedFor + "JanusGraphConnectionPool") .setPort(port) .setMaxOperationsPerConnection(maxOperationsPerConnection) .setMaxConnsPerHost(maxConnsPerHost) .setRetryDelaySlice(retryDelaySlice) .setRetryMaxDelaySlice(retryMaxDelaySlice) .setRetrySuspendWindow(retrySuspendWindow) .setSocketTimeout(connectionTimeout) .setConnectTimeout(connectionTimeout) .setSeeds(StringUtils.join(hostnames, ",")); if (null != retryBackoffStrategy) { cpool.setRetryBackoffStrategy(retryBackoffStrategy); log.debug("Custom RetryBackoffStrategy {}", cpool.getRetryBackoffStrategy()); } else { log.debug("Default RetryBackoffStrategy {}", cpool.getRetryBackoffStrategy()); } if (StringUtils.isNotBlank(localDatacenter)) { cpool.setLocalDatacenter(localDatacenter); log.debug("Set local datacenter: {}", cpool.getLocalDatacenter()); } AstyanaxConfigurationImpl aconf = new AstyanaxConfigurationImpl() .setConnectionPoolType(poolType) .setDiscoveryType(discType) .setTargetCassandraVersion("1.2") .setMaxThriftSize(thriftFrameSizeBytes); if (0 < maxConnections) { cpool.setMaxConns(maxConnections); } if (hasAuthentication()) { cpool.setAuthenticationCredentials(new SimpleAuthenticationCredentials(username, password)); } if (config.get(SSL_ENABLED)) { cpool.setSSLConnectionContext(new SSLConnectionContext(config.get(SSL_TRUSTSTORE_LOCATION), config.get(SSL_TRUSTSTORE_PASSWORD))); } AstyanaxContext.Builder ctxBuilder = new AstyanaxContext.Builder(); // Standard context builder options ctxBuilder .forCluster(clusterName) .forKeyspace(keySpaceName) .withAstyanaxConfiguration(aconf) .withConnectionPoolConfiguration(cpool) .withConnectionPoolMonitor(new CountingConnectionPoolMonitor()); // Conditional context builder option: host supplier if (config.has(HOST_SUPPLIER)) { String hostSupplier = config.get(HOST_SUPPLIER); Supplier<List<Host>> supplier = null; if (hostSupplier != null) { try { supplier = (Supplier<List<Host>>) Class.forName(hostSupplier).newInstance(); ctxBuilder.withHostSupplier(supplier); } catch (Exception e) { log.warn("Problem with host supplier class " + hostSupplier + ", going to use default.", e); } } } return ctxBuilder; } private void ensureKeyspaceExists(Cluster cl) throws BackendException { KeyspaceDefinition ksDef; try { ksDef = cl.describeKeyspace(keySpaceName); if (null != ksDef && ksDef.getName().equals(keySpaceName)) { log.debug("Found keyspace {}", keySpaceName); return; } } catch (ConnectionException e) { log.debug("Failed to describe keyspace {}", keySpaceName); } log.debug("Creating keyspace {}...", keySpaceName); try { ksDef = cl.makeKeyspaceDefinition() .setName(keySpaceName) .setStrategyClass(storageConfig.get(REPLICATION_STRATEGY)) .setStrategyOptions(strategyOptions); cl.addKeyspace(ksDef); log.debug("Created keyspace {}", keySpaceName); } catch (ConnectionException e) { log.debug("Failed to create keyspace {}", keySpaceName); throw new TemporaryBackendException(e); } } private static RetryBackoffStrategy getRetryBackoffStrategy(String desc) throws PermanentBackendException { if (null == desc) return null; String[] tokens = desc.split(","); String policyClassName = tokens[0]; int argCount = tokens.length - 1; Integer[] args = new Integer[argCount]; for (int i = 1; i < tokens.length; i++) { args[i - 1] = Integer.valueOf(tokens[i]); } try { RetryBackoffStrategy rbs = instantiate(policyClassName, args, desc); log.debug("Instantiated RetryBackoffStrategy object {} from config string \"{}\"", rbs, desc); return rbs; } catch (Exception e) { throw new PermanentBackendException("Failed to instantiate Astyanax RetryBackoffStrategy implementation", e); } } private static RetryPolicy getRetryPolicy(String serializedRetryPolicy) throws BackendException { String[] tokens = serializedRetryPolicy.split(","); String policyClassName = tokens[0]; int argCount = tokens.length - 1; Integer[] args = new Integer[argCount]; for (int i = 1; i < tokens.length; i++) { args[i - 1] = Integer.valueOf(tokens[i]); } try { RetryPolicy rp = instantiate(policyClassName, args, serializedRetryPolicy); log.debug("Instantiated RetryPolicy object {} from config string \"{}\"", rp, serializedRetryPolicy); return rp; } catch (Exception e) { throw new PermanentBackendException("Failed to instantiate Astyanax Retry Policy class", e); } } @SuppressWarnings("unchecked") private static <V> V instantiate(String policyClassName, Integer[] args, String raw) throws Exception { for (Constructor<?> con : Class.forName(policyClassName).getConstructors()) { Class<?>[] parameterTypes = con.getParameterTypes(); // match constructor by number of arguments first if (args.length != parameterTypes.length) continue; // check if the constructor parameter types are compatible with argument types (which are integer) // note that we allow long.class arguments too because integer is cast to long by runtime. boolean intsOrLongs = true; for (Class<?> pc : parameterTypes) { if (!pc.equals(int.class) && !pc.equals(long.class)) { intsOrLongs = false; break; } } // we found a constructor with required number of parameters but times didn't match, let's carry on if (!intsOrLongs) continue; if (log.isDebugEnabled()) log.debug("About to instantiate class {} with {} arguments", con.toString(), args.length); return (V) con.newInstance(args); } throw new Exception("Failed to identify a class matching the Astyanax Retry Policy config string \"" + raw + "\""); } @Override public Map<String, String> getCompressionOptions(String cf) throws BackendException { try { Keyspace k = keyspaceContext.getClient(); KeyspaceDefinition kdef = k.describeKeyspace(); if (null == kdef) { throw new PermanentBackendException("Keyspace " + k.getKeyspaceName() + " is undefined"); } ColumnFamilyDefinition cfdef = kdef.getColumnFamily(cf); if (null == cfdef) { throw new PermanentBackendException("Column family " + cf + " is undefined"); } return cfdef.getCompressionOptions(); } catch (ConnectionException e) { throw new PermanentBackendException(e); } } }