From 85248afbf9abaeab124881a8eda68cfee43bff3b Mon Sep 17 00:00:00 2001 From: Jeremy Chapman Date: Tue, 12 Jun 2018 11:44:32 -0700 Subject: [PATCH 1/3] Update dependencies and shade cassandra-all usage. Astyanax is deprecated but still used by a lot of projects and Astyanax's very old dependencies have been making it difficult to migrate projects from Astyanax to the DataStax driver. The migration can be especially difficult for projects that need Astyanax and DataStax drivers to exist for some time (e.g. an app that wants to migrate its own Astyanax dependency to DataStax driver but still has other transitive dependencies to Astyanax). * Shaded cassandra-all to avoid dependency incompatibilities with newer versions of cassandra-unit/CassandraDaemon/EmbeddedCassandra for unit/integration testing. Relocates astyanax-cassandra's "org.apache.cassandra" dependencies to "com.netflix.astyanax.org.apache.cassandra." * Updated gradle wrapper to 4.1 * Updated all explicit dependencies to newer compatible versions. * Tested with Guava versions 19-25. * Eliminated/refactored some dependency declarations to reflect true dependencies and minimize transitive dependencies forced on Astyanax users. * Moved dependencies not actually used by src/main to "testCompile" to keep them out of user's transitive dependencies. * Added missing license headers. * Updated readme to more explicitly suggest Astyanax users migrate to DataStax driver. * Note that with shading cassandra-all, currently IntelliJ will incorrectly render "com.netflix.astyanax.org.apache.cassandra." as unfound. This is a known IntelliJ bug. Astyanax still builds perfectly fine via Gradle. --- Readme.markdown | 53 ++- astyanax-cassandra-all-shaded/build.gradle | 41 ++ astyanax-cassandra/build.gradle | 18 +- .../impl/AstyanaxConfigurationImpl.java | 6 +- .../astyanax/model/AbstractComposite.java | 3 +- .../partitioner/BigInteger127Partitioner.java | 2 +- .../partitioner/Murmur3Partitioner.java | 3 +- .../astyanax/serializers/AsciiSerializer.java | 2 +- .../serializers/BigDecimalSerializer.java | 2 +- .../serializers/BigIntegerSerializer.java | 2 +- .../serializers/BooleanSerializer.java | 2 +- .../serializers/ByteBufferSerializer.java | 2 +- .../astyanax/serializers/ByteSerializer.java | 2 +- .../serializers/BytesArraySerializer.java | 2 +- .../astyanax/serializers/ComparatorType.java | 64 +-- .../astyanax/serializers/DateSerializer.java | 2 +- .../serializers/DoubleSerializer.java | 2 +- .../astyanax/serializers/FloatSerializer.java | 2 +- .../serializers/GzipStringSerializer.java | 2 +- .../astyanax/serializers/ListSerializer.java | 6 +- .../astyanax/serializers/LongSerializer.java | 2 +- .../astyanax/serializers/MapSerializer.java | 8 +- .../serializers/SerializerPackageImpl.java | 16 +- .../astyanax/serializers/SetSerializer.java | 4 +- .../astyanax/serializers/ShortSerializer.java | 6 +- .../serializers/SnappyStringSerializer.java | 2 +- .../SpecificCompositeSerializer.java | 9 +- .../SpecificReversedSerializer.java | 8 +- .../serializers/StringSerializer.java | 2 +- .../serializers/TimeUUIDSerializer.java | 3 +- .../astyanax/serializers/UUIDSerializer.java | 2 +- .../db/marshal/ShadedTypeParser.java | 411 ++++++++++++++++++ .../astyanax/util/ColumnarRecordWriter.java | 2 +- .../astyanax/util/CsvColumnReader.java | 2 +- .../astyanax/util/CsvRecordReader.java | 2 +- .../netflix/astyanax/util/RecordReader.java | 2 +- .../netflix/astyanax/util/RecordWriter.java | 2 +- .../main/resources/cassandra2-template.yaml | 12 +- astyanax-contrib/build.gradle | 13 +- astyanax-core/build.gradle | 12 +- astyanax-cql/build.gradle | 12 +- .../astyanax/cql/JavaDriverConfigBridge.java | 44 +- .../astyanax/cql/JavaDriverConfigBuilder.java | 30 +- .../cql/reads/model/CqlColumnImpl.java | 43 +- .../ChangeConsistencyLevelRetryPolicy.java | 18 + .../cql/schema/CqlColumnDefinitionImpl.java | 2 +- astyanax-entity-mapper/build.gradle | 15 +- .../entitystore/CompositeEntityMapper.java | 6 +- astyanax-examples/build.gradle | 8 +- .../astyanax/examples/AstCQLClient.java | 52 ++- .../netflix/astyanax/examples/AstClient.java | 63 ++- astyanax-queue/build.gradle | 14 +- .../queue/ShardedDistributedMessageQueue.java | 23 +- astyanax-recipes/build.gradle | 13 +- astyanax-test/build.gradle | 31 +- .../cql/test/SerializerPackageTests.java | 16 +- .../test/utils/AstyanaxContextFactory.java | 33 +- .../main/resources/cassandra2-template.yaml | 12 +- .../CompositeEntityManagerTest.java | 2 +- .../astyanax/entitystore/SampleEntity.java | 2 +- .../astyanax/recipes/LockRecipeTest.java | 2 +- .../astyanax/recipes/MiscUnitTest.java | 47 +- .../astyanax/serializers/SerializersTest.java | 12 +- .../com/netflix/astyanax/thrift/CqlTest.java | 2 +- .../thrift/ThriftKeyspaceImplTest.java | 4 +- astyanax-thrift/build.gradle | 13 +- .../thrift/ThriftAllRowsQueryImpl.java | 2 +- .../model/ThriftCounterColumnListImpl.java | 19 +- astyanax/build.gradle | 9 + build.gradle | 13 +- dependency-versions.gradle | 52 ++- gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 52928 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 57 +-- gradlew.bat | 14 +- settings.gradle | 4 +- 76 files changed, 990 insertions(+), 441 deletions(-) create mode 100644 astyanax-cassandra-all-shaded/build.gradle create mode 100644 astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java mode change 100644 => 100755 gradlew.bat diff --git a/Readme.markdown b/Readme.markdown index 875be9981..ea5e34022 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -3,7 +3,24 @@ Astyanax Astyanax is a high level Java client for [Apache Cassandra](http://cassandra.apache.org). Apache Cassandra is a highly available column oriented database. -Astyanax is currently in use at [Netflix](http://movies.netflix.com), but is being [retired](https://medium.com/netflix-techblog/astyanax-retiring-an-old-friend-6cca1de9ac4). Fixes will be made for serious issues, but it is not under active development. +Astyanax is currently in use at [Netflix](http://movies.netflix.com), but is being [retired](https://medium.com/netflix-techblog/astyanax-retiring-an-old-friend-6cca1de9ac4). +Astyanax is no longer under active development, but may receive dependency updates to ease migration away from Astyanax. + +In place of Astyanax consider using [DataStax Java Driver for Apache Cassandra](https://github.com/datastax/java-driver) +which is under active development and encapsulates a lot of lessons learned from Astyanax. The DataStax driver supports +only CQL protocol because Apache Cassandra 4.x drops the Thrift protocol. Switching to the DataStax driver requires +using CQL queries, but you'll still be able to access legacy column families via the CQL "with compact storage" option. + +This version of Astyanax shades its dependency on cassandra-all so you can optionally select any version +of cassandra-unit you like for unit/integration testing with different versions of Cassandra. When upgrading to this +verion of Astyanax you may need to: +* Explicitly add any cassandra-all transitive dependencies you previously silently depended on via Astayanax transitives. +* If you were using internal features of Astyanax that (unintentionally) publicly exposed cassandra-all classes, +you must either: + * Option A (best): Migrate away from Astyanax to [DataStax Java Driver for Apache Cassandra](https://github.com/datastax/java-driver). + * Option B (second-best): Use only astyanax-core public interfaces (none of them expose cassandra-all classes). + * Option C: Switch your objects from "org.apache.cassandra" to the shaded "com.netflix.astyanax.shaded.org.apache.cassandra" + package Astyanax now depends on. Artifacts ------------------------------- @@ -12,19 +29,26 @@ Astyanax jars are published to Maven Central. As of astyanax 1.56.27 the projec Required artifacts -|GroupID/Org|ArtifactID/Name| -| --------- | ------------- | -|com.netflix.astyanax|astyanax-core| -|com.netflix.astyanax|astyanax-thrift| -|com.netflix.astyanax|astyanax-cassandra| +|GroupID/Org|ArtifactID/Name|Desc| +| --------- | ------------- |----| +|com.netflix.astyanax|astyanax-thrift or astyanax-cql|Choose Thrift or CQL protocol. Note Cassandra 4.x+ drops support for Thrift protocol.| + +Transitive artifacts (dependencies automatically added via a required artifact) + +|GroupID/Org|ArtifactID/Name|Desc| +| --------- | ------------- |----| +|com.netflix.astyanax|astyanax-core|Astyanax's public interface.| +|com.netflix.astyanax|astyanax-cassandra|Cassandra-specific features shared by astyanax-thrift and astyanax-cql| +|com.netflix.astyanax|astyanax-cassandra-all-shaded|Shaded version of cassandra-all for the few classes used by astyanax-cassandra so projects are free to select arbitrary versions of cassandra-unit for unit/integration testing against newer versions of Cassandra. Hides Astyanax's dependency on cassandra-all by refactoring "org.apache.cassandra" classes to "com.netflix.astyanax.shaded.org.apache.cassandra".| Optional artifacts -|GroupID/Org|ArtifactID/Name| -| --------- | ------------- | -|com.netflix.astyanax|astyanax-queue| -|com.netflix.astyanax|astyanax-entity-mapper| -|com.netflix.astyanax|astyanax-recipes| +|GroupID/Org|ArtifactID/Name|Desc| +| --------- | ------------- |----| +|com.netflix.astyanax|astyanax-contrib|Optional integration with other commonly-used Netflix OSS modules.| +|com.netflix.astyanax|astyanax-queue|Queue implementation backed by Cassandra storage. Use at your own risk -- Cassandra can be a *very bad* storage engine for queues. If you insist on using Cassandra for queues, set a very short TTL and use DateTieredCompactionStrategy (DTCS).| +|com.netflix.astyanax|astyanax-entity-mapper|| +|com.netflix.astyanax|astyanax-recipes|Optional implementations of some common patterns. Use at your own risk; some of these are popular but not well-suite for Cassandra for many use cases (looking at you, AllRowsReader).| Features -------- @@ -45,6 +69,13 @@ Documentation ------------- Detailed documentation of Astyanax's features and usage can be found on the [wiki](https://github.com/Netflix/astyanax/wiki) and the [getting started guide](https://github.com/Netflix/astyanax/wiki/Getting-Started). +IntelliJ: currently (June 2018) IntelliJ has a bug which renders an "unfound" error for packages relocated via another +module in the same project (e.g. shaded classes from astyanax-cassandra-all-shaded). +This affects *only* the Astyanax project itself within IntelliJ; Astyanax still builds perfectly fine via Gradle. +Astyanax users are unaffected. For more details see: + +* [https://github.com/johnrengelman/shadow/issues/264](https://github.com/johnrengelman/shadow/issues/264) +* [https://youtrack.jetbrains.com/issue/IDEA-163411](https://youtrack.jetbrains.com/issue/IDEA-163411) Ancient History --------------- diff --git a/astyanax-cassandra-all-shaded/build.gradle b/astyanax-cassandra-all-shaded/build.gradle new file mode 100644 index 000000000..e9b6c3aef --- /dev/null +++ b/astyanax-cassandra-all-shaded/build.gradle @@ -0,0 +1,41 @@ +/** + * Shaded version of cassandra-all for the few classes used by astyanax-cassandra. Shading frees projects to select + * arbitrary versions of cassandra-unit for unit/integration testing against newer versions of Cassandra. + * Hides Astyanax's dependency on cassandra-all by refactoring "org.apache.cassandra" classes to + * "com.netflix.astyanax.shaded.org.apache.cassandra". + */ + +plugins { + id 'com.github.johnrengelman.plugin-shadow' version '2.0.3' + id 'java' +} + +print "Shading cassandra-all for cassandraVersion=${cassandraVersion}\n" + +dependencies { + compile ("org.apache.cassandra:cassandra-all:$cassandraVersion") { + // Exclude all those heavy transitive dependencies because Astyanax doesn't need them + transitive = false + } +} + +shadowJar { + // Don't append default "-all" to end of shaded jar name. + classifier = '' + + // Add only the shaded cassandra-all classes + dependencies { + include( + dependency('org.apache.cassandra:cassandra-all::.*') + ) + } + + // Shaded/rename cassandra-all class packages + relocate("org.apache.cassandra", "com.netflix.astyanax.shaded.org.apache.cassandra") +} + +tasks.build.dependsOn(shadowJar) + +// Create only the shadow jar containing the shaded classes +tasks.jar.setEnabled(false) +tasks.jar.dependsOn(tasks.shadowJar) diff --git a/astyanax-cassandra/build.gradle b/astyanax-cassandra/build.gradle index 0cbbbccba..fc2253025 100644 --- a/astyanax-cassandra/build.gradle +++ b/astyanax-cassandra/build.gradle @@ -1,19 +1,19 @@ apply plugin: 'osgi' +/** + * Cassandra-specific features shared by astyanax-thrift and astyanax-cql. + */ dependencies { - compile project(':astyanax-core') - compile ("org.apache.cassandra:cassandra-all:$cassandraVersion") { - force = true - transitive = false - } + compile project(':astyanax-core') + + // Shaded version of cassandra-all for the few simple classes Astyanax actually uses. + // Also excludes cassandra-all's transitive dependencies. + compile project(path: ':astyanax-cassandra-all-shaded', configuration: 'shadow') + compile "org.xerial.snappy:snappy-java:$snappyVersion" compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion" - compile "org.apache.cassandra:cassandra-thrift:$cassandraThriftVersion" compile "org.apache.servicemix.bundles:org.apache.servicemix.bundles.commons-csv:$commonsCsvVersion" compile "org.codehaus.jettison:jettison:$jettisonVersion" compile "commons-codec:commons-codec:$commonsCodecVersion" compile "org.slf4j:slf4j-api:$slf4jVersion" - testCompile "junit:junit:$junitVersion" } - - diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/impl/AstyanaxConfigurationImpl.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/impl/AstyanaxConfigurationImpl.java index 124fd65c3..e3f8631ad 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/impl/AstyanaxConfigurationImpl.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/impl/AstyanaxConfigurationImpl.java @@ -53,9 +53,11 @@ public class AstyanaxConfigurationImpl implements AstyanaxConfiguration { private int maxThriftSize = 16384000; public AstyanaxConfigurationImpl() { - partitioners.put(org.apache.cassandra.dht.RandomPartitioner.class.getCanonicalName(), BigInteger127Partitioner.get()); + partitioners.put("org.apache.cassandra.dht.RandomPartitioner", + BigInteger127Partitioner.get()); try { - partitioners.put(org.apache.cassandra.dht.Murmur3Partitioner.class.getCanonicalName(), Murmur3Partitioner.get()); + partitioners.put("org.apache.cassandra.dht.Murmur3Partitioner", + Murmur3Partitioner.get()); } catch (NoClassDefFoundError exception) { // We ignore this for backwards compatiblity with pre 1.2 cassandra. diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/model/AbstractComposite.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/model/AbstractComposite.java index 99daa3c1a..440be0584 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/model/AbstractComposite.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/model/AbstractComposite.java @@ -27,8 +27,7 @@ import java.util.Map; import java.util.logging.Logger; -import org.apache.cassandra.utils.ByteBufferUtil; - +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.ByteBufferUtil; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableClassToInstanceMap; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/BigInteger127Partitioner.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/BigInteger127Partitioner.java index c95288213..a9d8b4c2e 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/BigInteger127Partitioner.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/BigInteger127Partitioner.java @@ -21,7 +21,7 @@ import java.util.Iterator; import java.util.List; -import org.apache.cassandra.dht.RandomPartitioner; +import com.netflix.astyanax.shaded.org.apache.cassandra.dht.RandomPartitioner; import com.google.common.collect.Lists; import com.netflix.astyanax.Serializer; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/Murmur3Partitioner.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/Murmur3Partitioner.java index fd1785009..10e4099d2 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/Murmur3Partitioner.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/partitioner/Murmur3Partitioner.java @@ -32,7 +32,8 @@ public class Murmur3Partitioner implements Partitioner { public static final BigInteger ONE = new BigInteger("1"); - private static final org.apache.cassandra.dht.Murmur3Partitioner partitioner = new org.apache.cassandra.dht.Murmur3Partitioner(); + private static final com.netflix.astyanax.shaded.org.apache.cassandra.dht.Murmur3Partitioner partitioner = + new com.netflix.astyanax.shaded.org.apache.cassandra.dht.Murmur3Partitioner(); private static final Murmur3Partitioner instance = new Murmur3Partitioner(); public static Partitioner get() { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/AsciiSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/AsciiSerializer.java index d98b88d5c..496fdafac 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/AsciiSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/AsciiSerializer.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.apache.cassandra.db.marshal.AsciiType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AsciiType; /** * Almost identical to StringSerializer except we use the US-ASCII character set diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigDecimalSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigDecimalSerializer.java index 51669a188..cfd05f9c4 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigDecimalSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigDecimalSerializer.java @@ -18,7 +18,7 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.DecimalType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.DecimalType; public class BigDecimalSerializer extends AbstractSerializer { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigIntegerSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigIntegerSerializer.java index 8756934de..8c8bb8951 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigIntegerSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BigIntegerSerializer.java @@ -18,7 +18,7 @@ import java.math.BigInteger; import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.IntegerType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.IntegerType; /** * Serializer implementation for BigInteger diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BooleanSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BooleanSerializer.java index e69de7a90..f4899e889 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BooleanSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BooleanSerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.BooleanType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.BooleanType; /** * Converts bytes to Boolean and vice versa diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteBufferSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteBufferSerializer.java index dc7941c3a..4df7b49d6 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteBufferSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteBufferSerializer.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import org.apache.cassandra.db.marshal.BytesType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.BytesType; /** * The BytesExtractor is a simple identity function. It supports the Extractor diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteSerializer.java index b5ee5de5a..1f31eca06 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ByteSerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.IntegerType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.IntegerType; public class ByteSerializer extends AbstractSerializer { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BytesArraySerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BytesArraySerializer.java index 3ffe6666b..ac17b0865 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BytesArraySerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/BytesArraySerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.BytesType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.BytesType; import com.netflix.astyanax.Serializer; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ComparatorType.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ComparatorType.java index 975a5f548..cc0130e4b 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ComparatorType.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ComparatorType.java @@ -15,32 +15,35 @@ */ package com.netflix.astyanax.serializers; -import com.netflix.astyanax.*; +import com.netflix.astyanax.Serializer; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ShadedTypeParser; + +import static com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ShadedTypeParser.SHADED_PREFIX; /** * @author: peter */ public enum ComparatorType { - ASCIITYPE("org.apache.cassandra.db.marshal.AsciiType", AsciiSerializer.get()), - BYTESTYPE("org.apache.cassandra.db.marshal.BytesType", ByteBufferSerializer.get()), - INTEGERTYPE("org.apache.cassandra.db.marshal.IntegerType", BigIntegerSerializer.get()), - INT32TYPE("org.apache.cassandra.db.marshal.Int32Type", Int32Serializer.get()), - DECIMALTYPE("org.apache.cassandra.db.marshal.DecimalType", BigDecimalSerializer.get()), - LEXICALUUIDTYPE("org.apache.cassandra.db.marshal.LexicalUUIDType", UUIDSerializer.get()), - LOCALBYPARTITIONERTYPE("org.apache.cassandra.db.marshal.LocalByPartionerType", ByteBufferSerializer.get()), // FIXME - LONGTYPE("org.apache.cassandra.db.marshal.LongType", LongSerializer.get()), - TIMEUUIDTYPE("org.apache.cassandra.db.marshal.TimeUUIDType", TimeUUIDSerializer.get()), - UTF8TYPE("org.apache.cassandra.db.marshal.UTF8Type", StringSerializer.get()), - COMPOSITETYPE("org.apache.cassandra.db.marshal.CompositeType", CompositeSerializer.get()), - DYNAMICCOMPOSITETYPE("org.apache.cassandra.db.marshal.DynamicCompositeType", DynamicCompositeSerializer.get()), - UUIDTYPE("org.apache.cassandra.db.marshal.UUIDType", UUIDSerializer.get()), - COUNTERTYPE("org.apache.cassandra.db.marshal.CounterColumnType", LongSerializer.get()), - DOUBLETYPE("org.apache.cassandra.db.marshal.DoubleType", DoubleSerializer.get()), - FLOATTYPE("org.apache.cassandra.db.marshal.FloatType", FloatSerializer.get()), - BOOLEANTYPE("org.apache.cassandra.db.marshal.BooleanType", BooleanSerializer.get()), - DATETYPE("org.apache.cassandra.db.marshal.DateType", DateSerializer.get()), - REVERSEDTYPE("org.apache.cassandra.db.marshal.ReversedType", ReversedSerializer.get()); + ASCIITYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.AsciiType", AsciiSerializer.get()), + BYTESTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.BytesType", ByteBufferSerializer.get()), + INTEGERTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.IntegerType", BigIntegerSerializer.get()), + INT32TYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.Int32Type", Int32Serializer.get()), + DECIMALTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.DecimalType", BigDecimalSerializer.get()), + LEXICALUUIDTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.LexicalUUIDType", UUIDSerializer.get()), + LOCALBYPARTITIONERTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.LocalByPartionerType", ByteBufferSerializer.get()), // FIXME + LONGTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.LongType", LongSerializer.get()), + TIMEUUIDTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.TimeUUIDType", TimeUUIDSerializer.get()), + UTF8TYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.UTF8Type", StringSerializer.get()), + COMPOSITETYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.CompositeType", CompositeSerializer.get()), + DYNAMICCOMPOSITETYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.DynamicCompositeType", DynamicCompositeSerializer.get()), + UUIDTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.UUIDType", UUIDSerializer.get()), + COUNTERTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.CounterColumnType", LongSerializer.get()), + DOUBLETYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.DoubleType", DoubleSerializer.get()), + FLOATTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.FloatType", FloatSerializer.get()), + BOOLEANTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.BooleanType", BooleanSerializer.get()), + DATETYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.DateType", DateSerializer.get()), + REVERSEDTYPE(SHADED_PREFIX + "org.apache.cassandra.db.marshal.ReversedType", ReversedSerializer.get()); private final String className; private final String typeName; @@ -48,12 +51,7 @@ public enum ComparatorType { private ComparatorType(String className, Serializer serializer) { this.className = className; - if (className.startsWith("org.apache.cassandra.db.marshal.")) { - typeName = className.substring("org.apache.cassandra.db.marshal.".length()); - } - else { - typeName = className; - } + this.typeName = getShadedTypeName(className); this.serializer = serializer; } @@ -73,15 +71,19 @@ public static ComparatorType getByClassName(String className) { if (className == null) { return null; } - for (ComparatorType type : ComparatorType.values()) { - if (type.getClassName().equals(className)) { - return type; - } - if (type.getClassName().equals("org.apache.cassandra.db.marshal." + className)) { + if (type.getClassName().equals(getShadedClassName(className))) { return type; } } return null; } + + public static String getShadedClassName(String className){ + return ShadedTypeParser.getShadedClassName(className); + } + + public static String getShadedTypeName(String typeName){ + return ShadedTypeParser.getShadedTypeName(typeName); + } } diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DateSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DateSerializer.java index 601f6f679..c9d5cf069 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DateSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DateSerializer.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import java.util.Date; -import org.apache.cassandra.db.marshal.DateType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.DateType; /** * Converts bytes to Date and vice versa, by first converting the Date to or diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DoubleSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DoubleSerializer.java index 7a6494836..c577f0781 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DoubleSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/DoubleSerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.DoubleType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.DoubleType; /** * Uses LongSerializer via translating Doubles to and from raw long bytes form. diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/FloatSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/FloatSerializer.java index 51efc1b25..90c8d0442 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/FloatSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/FloatSerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.FloatType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.FloatType; /** * Uses IntSerializer via translating Float objects to and from raw long bytes diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/GzipStringSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/GzipStringSerializer.java index 91146e1ed..a82c23268 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/GzipStringSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/GzipStringSerializer.java @@ -23,7 +23,7 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import org.apache.commons.codec.binary.StringUtils; public class GzipStringSerializer extends AbstractSerializer { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ListSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ListSerializer.java index 31277b801..3924e531c 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ListSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ListSerializer.java @@ -18,10 +18,8 @@ import java.nio.ByteBuffer; import java.util.List; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ListType; - -import com.netflix.astyanax.serializers.AbstractSerializer; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ListType; /** * Serializer implementation for generic lists. diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/LongSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/LongSerializer.java index 32033b81e..5e8efbfdc 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/LongSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/LongSerializer.java @@ -17,7 +17,7 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.LongType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.LongType; /** * Converts bytes to Long and vise a versa diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/MapSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/MapSerializer.java index 8b7f0093d..8b030102e 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/MapSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/MapSerializer.java @@ -15,14 +15,12 @@ */ package com.netflix.astyanax.serializers; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.MapType; + import java.nio.ByteBuffer; import java.util.Map; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.MapType; - -import com.netflix.astyanax.serializers.AbstractSerializer; - /** * Serializer implementation for generic maps. * diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SerializerPackageImpl.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SerializerPackageImpl.java index 489ac8e66..264183fc2 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SerializerPackageImpl.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SerializerPackageImpl.java @@ -23,16 +23,15 @@ import java.util.Map.Entry; import java.util.Set; -import org.apache.cassandra.db.marshal.CompositeType; -import org.apache.cassandra.db.marshal.ReversedType; -import org.apache.cassandra.db.marshal.TypeParser; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.CompositeType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ReversedType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ShadedTypeParser; import org.apache.commons.lang.StringUtils; import com.netflix.astyanax.Serializer; import com.netflix.astyanax.SerializerPackage; import com.netflix.astyanax.ddl.ColumnDefinition; import com.netflix.astyanax.ddl.ColumnFamilyDefinition; -//import org.apache.cassandra.config.ConfigurationException; /** * Basic implementation of SerializerPackage which can be configured either from @@ -118,7 +117,7 @@ public SerializerPackageImpl setKeyType(String keyType) throws UnknownComparator if (type == ComparatorType.COMPOSITETYPE) { try { - this.keySerializer = new SpecificCompositeSerializer((CompositeType) TypeParser.parse(keyType)); + this.keySerializer = new SpecificCompositeSerializer((CompositeType) ShadedTypeParser.parse(keyType)); return this; } catch (Exception e) { @@ -132,7 +131,7 @@ else if (type == ComparatorType.DYNAMICCOMPOSITETYPE) { } else if (type == ComparatorType.REVERSEDTYPE) { try { - this.keySerializer = new SpecificReversedSerializer((ReversedType) TypeParser.parse(keyType)); + this.keySerializer = new SpecificReversedSerializer((ReversedType) ShadedTypeParser.parse(keyType)); return this; } catch (Exception e) { @@ -160,6 +159,7 @@ public SerializerPackageImpl setColumnType(String columnType) throws UnknownComp @SuppressWarnings("rawtypes") public SerializerPackageImpl setColumnNameType(String columnType) throws UnknownComparatorException { // Determine the column serializer + columnType = ComparatorType.getShadedClassName(columnType); String comparatorType = StringUtils.substringBefore(columnType, "("); ComparatorType type = ComparatorType.getByClassName(comparatorType); if (type == null) { @@ -168,7 +168,7 @@ public SerializerPackageImpl setColumnNameType(String columnType) throws Unknown if (type == ComparatorType.COMPOSITETYPE) { try { - this.columnSerializer = new SpecificCompositeSerializer((CompositeType) TypeParser.parse(columnType)); + this.columnSerializer = new SpecificCompositeSerializer((CompositeType) ShadedTypeParser.parse(columnType)); return this; } catch (Exception e) { @@ -182,7 +182,7 @@ else if (type == ComparatorType.DYNAMICCOMPOSITETYPE) { } else if (type == ComparatorType.REVERSEDTYPE) { try { - this.columnSerializer = new SpecificReversedSerializer((ReversedType) TypeParser.parse(columnType)); + this.columnSerializer = new SpecificReversedSerializer((ReversedType) ShadedTypeParser.parse(columnType)); return this; } catch (Exception e) { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SetSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SetSerializer.java index 7438644ba..5fc9531a0 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SetSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SetSerializer.java @@ -18,8 +18,8 @@ import java.nio.ByteBuffer; import java.util.Set; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.SetType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.SetType; import com.netflix.astyanax.serializers.AbstractSerializer; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ShortSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ShortSerializer.java index d6d05ee87..510e27ba1 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ShortSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/ShortSerializer.java @@ -17,11 +17,11 @@ import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.IntegerType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.IntegerType; /** - * {@link Serializer} for {@link Short}s (no pun intended). - * + * {@link com.netflix.astyanax.Serializer} for {@link Short}s (no pun intended). + * */ public final class ShortSerializer extends AbstractSerializer { diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SnappyStringSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SnappyStringSerializer.java index 334acbbe0..2f07935b4 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SnappyStringSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SnappyStringSerializer.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import org.apache.commons.codec.binary.StringUtils; import org.xerial.snappy.SnappyInputStream; import org.xerial.snappy.SnappyOutputStream; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificCompositeSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificCompositeSerializer.java index cadd90cf8..936f4e10b 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificCompositeSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificCompositeSerializer.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.CompositeType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.CompositeType; import com.google.common.base.Preconditions; @@ -34,10 +34,7 @@ public SpecificCompositeSerializer(CompositeType type) { comparators = new ArrayList( type.types.size() ); for ( AbstractType compType : type.types ) { String typeName = compType.toString(); - if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { - typeName = typeName.substring( "org.apache.cassandra.db.marshal.".length() ); - } - comparators.add( typeName ); + comparators.add( ComparatorType.getShadedTypeName(typeName) ); } } diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificReversedSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificReversedSerializer.java index af75db0bf..3f6623ac1 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificReversedSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/SpecificReversedSerializer.java @@ -16,8 +16,8 @@ package com.netflix.astyanax.serializers; import java.nio.ByteBuffer; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.ReversedType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ReversedType; import com.google.common.base.Preconditions; @SuppressWarnings("rawtypes") @@ -30,9 +30,7 @@ public SpecificReversedSerializer(ReversedType type) { Preconditions.checkNotNull(type); AbstractType compType = type.baseType; reversedTypeName = compType.toString(); - if ( reversedTypeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { - reversedTypeName = reversedTypeName.substring( "org.apache.cassandra.db.marshal.".length() ); - } + reversedTypeName = ComparatorType.getShadedTypeName(reversedTypeName); this.reversedComparatorType = ComparatorType.getByClassName(reversedTypeName); } diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/StringSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/StringSerializer.java index c812585f3..72d759172 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/StringSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/StringSerializer.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; /** * A StringSerializer translates the byte[] to and from string using utf-8 diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/TimeUUIDSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/TimeUUIDSerializer.java index fa20ef3bc..6b3b4ef9d 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/TimeUUIDSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/TimeUUIDSerializer.java @@ -16,12 +16,11 @@ package com.netflix.astyanax.serializers; import java.nio.ByteBuffer; -import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; -import org.apache.cassandra.db.marshal.TimeUUIDType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.TimeUUIDType; import com.netflix.astyanax.util.TimeUUIDUtils; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/UUIDSerializer.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/UUIDSerializer.java index 39e934c3f..ef30207aa 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/UUIDSerializer.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/serializers/UUIDSerializer.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import java.util.UUID; -import org.apache.cassandra.db.marshal.UUIDType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UUIDType; /** * A UUIDSerializer translates the byte[] to and from UUID types. diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java new file mode 100644 index 000000000..c41c7c1aa --- /dev/null +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java @@ -0,0 +1,411 @@ +/******************************************************************************* + * Copyright 2018 Netflix + * + * 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 com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal; + +import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.ConfigurationException; +import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.SyntaxException; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.ByteBufferUtil; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.FBUtilities; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.*; + +/** + * Extend TypeParser to support shaded {@link AbstractType} from shaded package. + * Converts input class names to shaded package name. + * + * Note that shading prefixes this class's package name with the shaded name {@link #SHADED_PREFIX}. + * + * Effectively uses {@link TypeParser} only as an interface. The implementation is a slightly altered copy-paste + * of {@link TypeParser} because {@link TypeParser#parse(String)} can't really be overridden. That method is public but + * references private fields (e.g. {@link TypeParser#idx}, etc.) and private methods + * ({@link TypeParser#getAbstractType}, etc.) which a derived class can't access. + * + * This is a very ugly way to support shading cassandra-all, but it's still the least invasive approach. + */ +public class ShadedTypeParser extends TypeParser { + public static final String SHADED_PREFIX = "com.netflix.astyanax.shaded."; + protected final String str; + protected int idx; + protected static final Map> cache = new HashMap(); + protected static final TypeParser EMPTY_PARSER = new ShadedTypeParser(""); + + public static ShadedTypeParser buildTypeParser(String str, int idx){ + return new ShadedTypeParser(str, idx); + } + + public ShadedTypeParser(String str) { + // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor + super(str); + this.str = str; + this.idx = 0; + } + + public ShadedTypeParser(String str, int idx) { + // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor + super(str); + this.str = str; + this.idx = idx; + } + + public static String getShadedClassName(String className){ + if(className.startsWith(SHADED_PREFIX)){ + return className; + } else if (className.contains(".")){ + return SHADED_PREFIX + className; + } else { + return SHADED_PREFIX + "org.apache.cassandra.db.marshal." + className; + } + } + + public static String getShadedTypeName(String typeName){ + if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( "org.apache.cassandra.db.marshal.".length() ); + } else if ( typeName.startsWith( SHADED_PREFIX + "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( (SHADED_PREFIX + "org.apache.cassandra.db.marshal.").length() ); + } + return typeName; + } + + public static AbstractType parse(String str) throws SyntaxException, ConfigurationException { + if (str == null) { + return BytesType.instance; + } else { + str = getShadedClassName(str); + AbstractType type = (AbstractType)cache.get(str); + if (type != null) { + return type; + } else { + int i = 0; + i = skipBlank(str, i); + + int j; + for(j = i; !isEOS(str, i) && isIdentifierChar(str.charAt(i)); ++i) { + ; + } + + if (i == j) { + return BytesType.instance; + } else { + String name = str.substring(j, i); + name = getShadedClassName(name); + i = skipBlank(str, i); + if (!isEOS(str, i) && str.charAt(i) == '(') { + ShadedTypeParser typeParser = buildTypeParser(str, i); + type = getAbstractType(name, typeParser); + } else { + type = getAbstractType(name); + } + + cache.put(str, type); + return type; + } + } + } + } + + public AbstractType parse() throws SyntaxException, ConfigurationException { + this.skipBlank(); + String name = this.readNextIdentifier(); + name = getShadedClassName(name); + this.skipBlank(); + return !this.isEOS() && this.str.charAt(this.idx) == '(' ? getAbstractType(name, this) : getAbstractType(name); + } + + public Map getKeyValueParameters() throws SyntaxException { + Map map = new HashMap(); + if (this.isEOS()) { + return map; + } else if (this.str.charAt(this.idx) != '(') { + throw new IllegalStateException(); + } else { + ++this.idx; + + String k; + String v; + for(; this.skipBlankAndComma(); map.put(k, v)) { + if (this.str.charAt(this.idx) == ')') { + ++this.idx; + return map; + } + + k = this.readNextIdentifier(); + v = ""; + this.skipBlank(); + if (this.str.charAt(this.idx) == '=') { + ++this.idx; + this.skipBlank(); + v = this.readNextIdentifier(); + } else if (this.str.charAt(this.idx) != ',' && this.str.charAt(this.idx) != ')') { + this.throwSyntaxError("unexpected character '" + this.str.charAt(this.idx) + "'"); + } + } + + throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } + } + + public List> getTypeParameters() throws SyntaxException, ConfigurationException { + List> list = new ArrayList(); + if (this.isEOS()) { + return list; + } else if (this.str.charAt(this.idx) != '(') { + throw new IllegalStateException(); + } else { + ++this.idx; + + while(this.skipBlankAndComma()) { + if (this.str.charAt(this.idx) == ')') { + ++this.idx; + return list; + } + + try { + list.add(this.parse()); + } catch (SyntaxException var4) { + SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); + ex.initCause(var4); + throw ex; + } + } + + throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } + } + + public Map> getAliasParameters() throws SyntaxException, ConfigurationException { + Map> map = new HashMap(); + if (this.isEOS()) { + return map; + } else if (this.str.charAt(this.idx) != '(') { + throw new IllegalStateException(); + } else { + ++this.idx; + + while(this.skipBlankAndComma()) { + if (this.str.charAt(this.idx) == ')') { + ++this.idx; + return map; + } + + String alias = this.readNextIdentifier(); + if (alias.length() != 1) { + this.throwSyntaxError("An alias should be a single character"); + } + + char aliasChar = alias.charAt(0); + if (aliasChar < '!' || aliasChar > 127) { + this.throwSyntaxError("An alias should be a single character in [0..9a..bA..B-+._&]"); + } + + this.skipBlank(); + if (this.str.charAt(this.idx) != '=' || this.str.charAt(this.idx + 1) != '>') { + this.throwSyntaxError("expecting '=>' token"); + } + + this.idx += 2; + this.skipBlank(); + + try { + map.put((byte)aliasChar, this.parse()); + } catch (SyntaxException var6) { + SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); + ex.initCause(var6); + throw ex; + } + } + + throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } + } + + public Map getCollectionsParameters() throws SyntaxException, ConfigurationException { + Map map = new HashMap(); + if (this.isEOS()) { + return map; + } else if (this.str.charAt(this.idx) != '(') { + throw new IllegalStateException(); + } else { + ++this.idx; + + while(this.skipBlankAndComma()) { + if (this.str.charAt(this.idx) == ')') { + ++this.idx; + return map; + } + + String bbHex = this.readNextIdentifier(); + ByteBuffer bb = null; + + try { + bb = ByteBufferUtil.hexToBytes(bbHex); + } catch (NumberFormatException var7) { + this.throwSyntaxError(var7.getMessage()); + } + + this.skipBlank(); + if (this.str.charAt(this.idx) != ':') { + this.throwSyntaxError("expecting ':' token"); + } + + ++this.idx; + this.skipBlank(); + + try { + AbstractType type = this.parse(); + if (!(type instanceof CollectionType)) { + throw new SyntaxException(type.toString() + " is not a collection type"); + } + + map.put(bb, (CollectionType)type); + } catch (SyntaxException var6) { + SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); + ex.initCause(var6); + throw ex; + } + } + + throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } + } + + protected static AbstractType getAbstractType(String compareWith) throws ConfigurationException { + String className = getShadedClassName(compareWith); + Class typeClass = FBUtilities.classForName(className, "abstract-type"); + + try { + Field field = typeClass.getDeclaredField("instance"); + return (AbstractType)field.get((Object)null); + } catch (NoSuchFieldException var4) { + return getRawAbstractType(typeClass, EMPTY_PARSER); + } catch (IllegalAccessException var5) { + return getRawAbstractType(typeClass, EMPTY_PARSER); + } + } + + protected static AbstractType getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException { + String className = getShadedClassName(compareWith); + Class typeClass = FBUtilities.classForName(className, "abstract-type"); + + AbstractType type; + try { + Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); + return (AbstractType)method.invoke((Object)null, parser); + } catch (NoSuchMethodException var6) { + type = getRawAbstractType(typeClass); + return AbstractType.parseDefaultParameters(type, parser); + } catch (IllegalAccessException var7) { + type = getRawAbstractType(typeClass); + return AbstractType.parseDefaultParameters(type, parser); + } catch (InvocationTargetException var8) { + ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); + ex.initCause(var8.getTargetException()); + throw ex; + } + } + + protected static AbstractType getRawAbstractType(Class> typeClass) throws ConfigurationException { + try { + Field field = typeClass.getDeclaredField("instance"); + return (AbstractType)field.get((Object)null); + } catch (NoSuchFieldException var2) { + throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); + } catch (IllegalAccessException var3) { + throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); + } + } + + protected static AbstractType getRawAbstractType(Class> typeClass, TypeParser parser) throws ConfigurationException { + try { + Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); + return (AbstractType)method.invoke((Object)null, parser); + } catch (NoSuchMethodException var4) { + throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); + } catch (IllegalAccessException var5) { + throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); + } catch (InvocationTargetException var6) { + ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); + ex.initCause(var6.getTargetException()); + throw ex; + } + } + + protected void throwSyntaxError(String msg) throws SyntaxException { + throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: %s", this.str, this.idx, msg)); + } + + protected boolean isEOS() { + return isEOS(this.str, this.idx); + } + + protected static boolean isEOS(String str, int i) { + return i >= str.length(); + } + + protected static boolean isBlank(int c) { + return c == 32 || c == 9 || c == 10; + } + + protected void skipBlank() { + this.idx = skipBlank(this.str, this.idx); + } + + protected static int skipBlank(String str, int i) { + while(!isEOS(str, i) && isBlank(str.charAt(i))) { + ++i; + } + + return i; + } + + protected boolean skipBlankAndComma() { + for(boolean commaFound = false; !this.isEOS(); ++this.idx) { + int c = this.str.charAt(this.idx); + if (c == ',') { + if (commaFound) { + return true; + } + + commaFound = true; + } else if (!isBlank(c)) { + return true; + } + } + + return false; + } + + protected static boolean isIdentifierChar(int c) { + return c >= 48 && c <= 57 || c >= 97 && c <= 122 || c >= 65 && c <= 90 || c == 45 || c == 43 || c == 46 || c == 95 || c == 38; + } + + public String readNextIdentifier() { + int i; + for(i = this.idx; !this.isEOS() && isIdentifierChar(this.str.charAt(this.idx)); ++this.idx) { + ; + } + + return this.str.substring(i, this.idx); + } + + public char readNextChar() { + this.skipBlank(); + return this.str.charAt(this.idx++); + } +} diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/ColumnarRecordWriter.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/ColumnarRecordWriter.java index 176b02d64..7d4578fe1 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/ColumnarRecordWriter.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/ColumnarRecordWriter.java @@ -19,7 +19,7 @@ import java.util.Iterator; import java.util.List; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import com.netflix.astyanax.ColumnListMutation; import com.netflix.astyanax.Keyspace; diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvColumnReader.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvColumnReader.java index fafbd4c8c..7eacf108d 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvColumnReader.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvColumnReader.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import org.apache.commons.csv.CSVParser; /** diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvRecordReader.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvRecordReader.java index c4d934eb8..540af9d58 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvRecordReader.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/CsvRecordReader.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import org.apache.commons.csv.CSVParser; /** diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordReader.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordReader.java index b6734de6d..080ed874e 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordReader.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordReader.java @@ -18,7 +18,7 @@ import java.io.IOException; import java.util.List; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; /** * diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordWriter.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordWriter.java index 1307b8d42..26aa4f25c 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordWriter.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/util/RecordWriter.java @@ -17,7 +17,7 @@ import java.util.List; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; diff --git a/astyanax-cassandra/src/main/resources/cassandra2-template.yaml b/astyanax-cassandra/src/main/resources/cassandra2-template.yaml index fe88ac21f..a14107f4c 100644 --- a/astyanax-cassandra/src/main/resources/cassandra2-template.yaml +++ b/astyanax-cassandra/src/main/resources/cassandra2-template.yaml @@ -28,7 +28,7 @@ seed_provider: - seeds: 127.0.0.1 concurrent_reads: 32 concurrent_writes: 64 -memtable_flush_queue_size: 4 +#memtable_flush_queue_size: 4 trickle_fsync: false trickle_fsync_interval_in_kb: 10240 storage_port: $STORAGE_PORT$ @@ -45,10 +45,10 @@ incremental_backups: false snapshot_before_compaction: false auto_snapshot: false column_index_size_in_kb: 64 -in_memory_compaction_limit_in_mb: 128 -multithreaded_compaction: false +#in_memory_compaction_limit_in_mb: 128 +#multithreaded_compaction: false compaction_throughput_mb_per_sec: 128 -compaction_preheat_key_cache: true +#compaction_preheat_key_cache: true read_request_timeout_in_ms: 10000 range_request_timeout_in_ms: 10000 write_request_timeout_in_ms: 10000 @@ -62,7 +62,7 @@ dynamic_snitch_badness_threshold: 0.1 request_scheduler: org.apache.cassandra.scheduler.NoScheduler index_interval: 256 inter_dc_tcp_nodelay: true -memtable_total_space_in_mb: 1024 +#memtable_total_space_in_mb: 1024 rpc_min_threads: 16 rpc_max_threads: 2048 stream_throughput_outbound_megabits_per_sec: '400' @@ -70,6 +70,6 @@ dynamic_snitch: true concurrent_compactors: 1 num_tokens: 1 auto_bootstrap: true -preheat_kernel_page_cache: false +#preheat_kernel_page_cache: false cas_contention_timeout_in_ms: 1000 file_cache_size_in_mb: '512' \ No newline at end of file diff --git a/astyanax-contrib/build.gradle b/astyanax-contrib/build.gradle index 047a58b81..cb565329a 100644 --- a/astyanax-contrib/build.gradle +++ b/astyanax-contrib/build.gradle @@ -1,12 +1,9 @@ +/** + * Optional integration with other commonly-used Netflix OSS modules. + */ dependencies { - compile project(':astyanax-cassandra') compile project(':astyanax-core') compile project(':astyanax-thrift') - compile 'com.netflix.eureka:eureka-client:1.1.110' - compile 'com.netflix.archaius:archaius-core:0.5.12' - compile ("com.google.guava:guava:$guavaVersion") { - force = true - } - compile "org.slf4j:slf4j-api:$slf4jVersion" - testCompile "junit:junit:$junitVersion" + compile 'com.netflix.eureka:eureka-client:1.9.0' + compile 'com.netflix.archaius:archaius-core:0.7.6' } diff --git a/astyanax-core/build.gradle b/astyanax-core/build.gradle index f44860df3..054bf9756 100644 --- a/astyanax-core/build.gradle +++ b/astyanax-core/build.gradle @@ -1,17 +1,13 @@ apply plugin: 'osgi' +/** + * Astyanax's public interface. + */ dependencies { compile "joda-time:joda-time:$jodaTimeVersion" compile "com.github.stephenc.high-scale-lib:high-scale-lib:$highScaleLibVersion" - compile ("com.google.guava:guava:$guavaVersion") { - force = true - } + compile "com.google.guava:guava:$guavaVersion" compile "com.eaio.uuid:uuid:$uuidVersion" compile "org.slf4j:slf4j-api:$slf4jVersion" compile "commons-lang:commons-lang:$commonsLangVersion" - testCompile "junit:junit:$junitVersion" - - compile("org.apache.cassandra:cassandra-all:$cassandraVersion") { - exclude group: 'org.apache.cassandra.deps' - } } diff --git a/astyanax-cql/build.gradle b/astyanax-cql/build.gradle index 73a1c431d..4d0e43c2b 100644 --- a/astyanax-cql/build.gradle +++ b/astyanax-cql/build.gradle @@ -1,18 +1,16 @@ apply plugin: 'osgi' +/** + * CQL protocol implementation of astyanax-core + */ dependencies { - compile project(':astyanax-core') - compile project(':astyanax-cassandra') + compile project(':astyanax-core') + compile project(':astyanax-cassandra') compile ("com.datastax.cassandra:cassandra-driver-core:$javaDriverVersion") { - //exclude group: 'com.google.guava', module: 'guava' transitive = false } - compile "io.netty:netty:$nettyVersion" - compile "com.google.guava:guava:$guavaVersion" compile "net.jpountz.lz4:lz4:$lz4Version" compile "com.codahale.metrics:metrics-core:$metricCoreVersion" compile "org.xerial.snappy:snappy-java:$snappyVersion" - } - diff --git a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBridge.java b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBridge.java index 13dc4e7fd..09deaaee0 100644 --- a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBridge.java +++ b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBridge.java @@ -15,16 +15,7 @@ */ package com.netflix.astyanax.cql; -import com.datastax.driver.core.AuthProvider; -import com.datastax.driver.core.Configuration; -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.HostDistance; -import com.datastax.driver.core.MetricsOptions; -import com.datastax.driver.core.PlainTextAuthProvider; -import com.datastax.driver.core.PoolingOptions; -import com.datastax.driver.core.ProtocolOptions; -import com.datastax.driver.core.QueryOptions; -import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.*; import com.datastax.driver.core.policies.LoadBalancingPolicy; import com.datastax.driver.core.policies.Policies; import com.datastax.driver.core.policies.RoundRobinPolicy; @@ -34,6 +25,8 @@ import com.netflix.astyanax.connectionpool.ConnectionPoolConfiguration; import com.netflix.astyanax.cql.util.ConsistencyLevelTransform; +import static com.datastax.driver.core.ProtocolOptions.DEFAULT_MAX_SCHEMA_AGREEMENT_WAIT_SECONDS; + public class JavaDriverConfigBridge { private final AstyanaxConfiguration asConfig; @@ -45,20 +38,21 @@ public JavaDriverConfigBridge(AstyanaxConfiguration asConfig, ConnectionPoolConf } public Configuration getJDConfig() { - - return new Configuration(getPolicies(), - getProtocolOptions(), - getPoolingOptions(), - getSocketOptions(), - getMetricsOptions(), - getQueryOptions()); + + return Configuration.builder() + .withPolicies(getPolicies()) + .withProtocolOptions(getProtocolOptions()) + .withPoolingOptions(getPoolingOptions()) + .withSocketOptions(getSocketOptions()) + .withMetricsOptions(getMetricsOptions()) + .withQueryOptions(getQueryOptions()) + .build(); } private Policies getPolicies() { - return new Policies(getLB(), - Policies.defaultReconnectionPolicy(), - Policies.defaultRetryPolicy(), - Policies.defaultAddressTranslater()); + return Policies.builder() + .withLoadBalancingPolicy(getLB()) + .build(); } private LoadBalancingPolicy getLB() { @@ -78,16 +72,16 @@ private LoadBalancingPolicy getLB() { private ProtocolOptions getProtocolOptions() { int port = cpConfig.getPort(); - int protocolVersion = -1; // use default - + AuthProvider authProvider = AuthProvider.NONE; AuthenticationCredentials creds = cpConfig.getAuthenticationCredentials(); if (creds != null) { authProvider = new PlainTextAuthProvider(creds.getUsername(), creds.getPassword()); } - - return new ProtocolOptions(port, protocolVersion, null, authProvider); + + return new ProtocolOptions(port, ProtocolVersion.NEWEST_SUPPORTED, DEFAULT_MAX_SCHEMA_AGREEMENT_WAIT_SECONDS, + null, authProvider); } private PoolingOptions getPoolingOptions() { diff --git a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBuilder.java b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBuilder.java index 60d49a3ce..ab9eb5ea4 100644 --- a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBuilder.java +++ b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/JavaDriverConfigBuilder.java @@ -66,20 +66,19 @@ public JavaDriverConfigBuilder() { } public JavaDriverConnectionPoolConfigurationImpl build() { - - Policies policies = new Policies(loadBalancingPolicy, reconnectionPolicy, retryPolicy); + Policies policies = Policies.builder() + .withLoadBalancingPolicy(loadBalancingPolicy) + .withReconnectionPolicy(reconnectionPolicy) + .withRetryPolicy(retryPolicy).build(); ProtocolOptions protocolOptions = (nativeProtocolPort == -1) ? new ProtocolOptions() : new ProtocolOptions(nativeProtocolPort); - PoolingOptions poolOptions = poolingOptions; - SocketOptions sockOptions = socketOptions; - MetricsOptions metricsOptions = new MetricsOptions(jmxReportingEnabled); - QueryOptions qOptions = queryOptions; - - return new JavaDriverConnectionPoolConfigurationImpl(new Configuration(policies, - protocolOptions, - poolOptions, - sockOptions, - metricsOptions, - qOptions)); + MetricsOptions metricsOptions = new MetricsOptions(true, jmxReportingEnabled); + return new JavaDriverConnectionPoolConfigurationImpl(Configuration.builder() + .withPolicies(policies) + .withProtocolOptions(protocolOptions) + .withPoolingOptions(poolingOptions) + .withSocketOptions(socketOptions) + .withMetricsOptions(metricsOptions) + .withQueryOptions(queryOptions).build()); } @@ -114,12 +113,13 @@ public JavaDriverConfigBuilder withMaxConnsPerHost(HostDistance distance, int ma } public JavaDriverConfigBuilder withMinRequestsPerConnection(HostDistance distance, int minRequests) { - this.poolingOptions.setMinSimultaneousRequestsPerConnectionThreshold(distance, minRequests); + + this.poolingOptions.setNewConnectionThreshold(distance, minRequests); return this; } public JavaDriverConfigBuilder withMaxRequestsPerConnection(HostDistance distance, int maxRequests) { - this.poolingOptions.setMaxSimultaneousRequestsPerConnectionThreshold(distance, maxRequests); + this.poolingOptions.setMaxRequestsPerConnection(distance, maxRequests); return this; } diff --git a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/reads/model/CqlColumnImpl.java b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/reads/model/CqlColumnImpl.java index 37c9bcfb0..54fd896bb 100644 --- a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/reads/model/CqlColumnImpl.java +++ b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/reads/model/CqlColumnImpl.java @@ -39,31 +39,31 @@ import com.netflix.astyanax.serializers.UUIDSerializer; /** - * Class that implements the {@link Column} interface. - * + * Class that implements the {@link Column} interface. + * * Note that since columns can be rows in CQL3, this class needs access to the java driver {@link Row} * within the java driver {@link ResultSet} - * - * The index provided within the row indicates where to start parsing the Column data. - * Also this class handles reading the TTL and Timestamp on the Column as well. - * + * + * The index provided within the row indicates where to start parsing the Column data. + * Also this class handles reading the TTL and Timestamp on the Column as well. + * * @author poberai * * @param */ public class CqlColumnImpl implements Column { - private Row row; - private C columnName; + private Row row; + private C columnName; private int index; - + private ComparatorType cType; - + private boolean isBlob = false; - + public CqlColumnImpl() { } - + public CqlColumnImpl(C colName, Row row, int index) { this.columnName = colName; this.row = row; @@ -72,9 +72,8 @@ public CqlColumnImpl(C colName, Row row, int index) { Definition colDefinition = row.getColumnDefinitions().asList().get(index); isBlob = colDefinition.getType() == DataType.blob(); } - + public CqlColumnImpl(C colName, Row row, int index, Definition colDefinition) { - this.columnName = colName; this.row = row; this.index = index; @@ -86,7 +85,7 @@ public CqlColumnImpl(C colName, Row row, int index, Definition colDefinition) { public C getName() { return columnName; } - + @Override public ByteBuffer getRawName() { return StringSerializer.get().toByteBuffer(String.valueOf(columnName)); @@ -158,9 +157,13 @@ public ByteBuffer getByteBufferValue() { return row.getBytes(index); } + /** + * @return {@link Date} from {@link com.datastax.driver.core.GettableByIndexData#getTimestamp(int)} for backwards- + * compatibility because this {@link #getTimestamp()} returns column timestamp, not value of a Date-based column. + */ @Override public Date getDateValue() { - return (isBlob) ? DateSerializer.get().fromByteBuffer(row.getBytes(index)) : row.getDate(index); + return (isBlob) ? DateSerializer.get().fromByteBuffer(row.getBytes(index)) : row.getTimestamp(index); } @Override @@ -189,19 +192,17 @@ public int getTtl() { public boolean hasValue() { return (row != null) && !(row.isNull(index)); } - public Object getGenericValue() { ComparatorType cType = getComparatorType(); return CqlTypeMapping.getDynamicColumn(row, cType.getSerializer(), index, null); } - + public ComparatorType getComparatorType() { - if (cType != null) { return cType; } - + // Lazy init DataType type = row.getColumnDefinitions().getType(index); if (type.isCollection()) { @@ -209,7 +210,7 @@ public ComparatorType getComparatorType() { } String typeString = (type.getName().name()).toUpperCase(); cType = CqlTypeMapping.getComparatorFromCqlType(typeString); - + return cType; } } diff --git a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/retrypolicies/ChangeConsistencyLevelRetryPolicy.java b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/retrypolicies/ChangeConsistencyLevelRetryPolicy.java index 511d5c1b9..7b126909a 100644 --- a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/retrypolicies/ChangeConsistencyLevelRetryPolicy.java +++ b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/retrypolicies/ChangeConsistencyLevelRetryPolicy.java @@ -15,9 +15,11 @@ */ package com.netflix.astyanax.cql.retrypolicies; +import com.datastax.driver.core.Cluster; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.Statement; import com.datastax.driver.core.WriteType; +import com.datastax.driver.core.exceptions.DriverException; import com.datastax.driver.core.policies.RetryPolicy.RetryDecision; import com.netflix.astyanax.cql.ConsistencyLevelMapping; @@ -112,6 +114,22 @@ public RetryDecision onUnavailable(Statement query, ConsistencyLevel cl, boolean shouldRetry = retryOnAllConditions || retryOnUnavailable; return checkRetry(query, cl, shouldRetry); } + + @Override + public RetryDecision onRequestError(Statement query, ConsistencyLevel cl, DriverException e, int nbRetry) { + boolean shouldRetry = retryOnAllConditions || retryOnUnavailable; + return checkRetry(query, cl, shouldRetry); + } + + @Override + public void init(Cluster cluster) { + // Do nothing + } + + @Override + public void close() { + // Do nothing + } }; @Override diff --git a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/schema/CqlColumnDefinitionImpl.java b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/schema/CqlColumnDefinitionImpl.java index 0eece6eb0..e5b3e8894 100644 --- a/astyanax-cql/src/main/java/com/netflix/astyanax/cql/schema/CqlColumnDefinitionImpl.java +++ b/astyanax-cql/src/main/java/com/netflix/astyanax/cql/schema/CqlColumnDefinitionImpl.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; diff --git a/astyanax-entity-mapper/build.gradle b/astyanax-entity-mapper/build.gradle index 01e5310e4..56f3ff020 100644 --- a/astyanax-entity-mapper/build.gradle +++ b/astyanax-entity-mapper/build.gradle @@ -1,13 +1,6 @@ dependencies { - compile project(':astyanax-core') - compile project(':astyanax-cassandra') - compile project(':astyanax-recipes') - compile "javax.persistence:persistence-api:$jpaVersion" - compile ("com.google.guava:guava:$guavaVersion") { - force = true - } - compile "org.slf4j:slf4j-api:$slf4jVersion" - compile "commons-lang:commons-lang:$commonsLangVersion" - testCompile project(':astyanax-thrift') - testCompile "junit:junit:$junitVersion" + compile project(':astyanax-core') + compile project(':astyanax-cassandra') + compile project(':astyanax-recipes') + compile "javax.persistence:persistence-api:$jpaVersion" } diff --git a/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/CompositeEntityMapper.java b/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/CompositeEntityMapper.java index 97b532081..90035ff28 100644 --- a/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/CompositeEntityMapper.java +++ b/astyanax-entity-mapper/src/main/java/com/netflix/astyanax/entitystore/CompositeEntityMapper.java @@ -300,7 +300,7 @@ public String toString() { } public String getKeyType() { - return idMapper.getSerializer().getComparatorType().getClassName(); + return idMapper.getSerializer().getComparatorType().getTypeName(); } /** @@ -359,7 +359,7 @@ public String getComparatorType() { sb.append(StringUtils.join( Collections2.transform(components, new Function, String>() { public String apply(FieldMapper input) { - return input.serializer.getComparatorType().getClassName(); + return input.serializer.getComparatorType().getTypeName(); } }), ",")); @@ -386,7 +386,7 @@ public static ByteBuffer getBytes(ByteBuffer bb, int length) { } String getValueType() { - return valueMapper.getSerializer().getComparatorType().getClassName(); + return valueMapper.getSerializer().getComparatorType().getTypeName(); } ByteBuffer[] getQueryEndpoints(Collection predicates) { diff --git a/astyanax-examples/build.gradle b/astyanax-examples/build.gradle index 8cc2b9643..0fea6f7bb 100644 --- a/astyanax-examples/build.gradle +++ b/astyanax-examples/build.gradle @@ -1,7 +1,7 @@ dependencies { - compile project(':astyanax-core') - compile project(':astyanax-cassandra') - compile project(':astyanax-thrift') - compile project(':astyanax-contrib') + compile project(':astyanax-core') + compile project(':astyanax-cassandra') + compile project(':astyanax-thrift') + compile project(':astyanax-contrib') compile "org.slf4j:slf4j-api:$slf4jVersion" } diff --git a/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstCQLClient.java b/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstCQLClient.java index c60e0732a..ee66a1c4b 100644 --- a/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstCQLClient.java +++ b/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstCQLClient.java @@ -17,6 +17,7 @@ import static com.netflix.astyanax.examples.ModelConstants.*; +import com.netflix.astyanax.ddl.KeyspaceDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +39,8 @@ import com.netflix.astyanax.serializers.StringSerializer; import com.netflix.astyanax.thrift.ThriftFamilyFactory; +import java.util.Properties; + /** * Example code for demonstrating how to access Cassandra using Astyanax and CQL3. * @@ -47,9 +50,10 @@ public class AstCQLClient { private static final Logger logger = LoggerFactory.getLogger(AstCQLClient.class); - private AstyanaxContext context; + private AstyanaxContext keyspaceContext; private Keyspace keyspace; private ColumnFamily EMP_CF; + private static final String KEYSPACE_NAME = "test1"; private static final String EMP_CF_NAME = "employees1"; private static final String INSERT_STATEMENT = String.format("INSERT INTO %s (%s, %s, %s, %s) VALUES (?, ?, ?, ?);", @@ -62,9 +66,9 @@ public class AstCQLClient { public void init() { logger.debug("init()"); - context = new AstyanaxContext.Builder() + keyspaceContext = new AstyanaxContext.Builder() .forCluster("Test Cluster") - .forKeyspace("test1") + .forKeyspace(KEYSPACE_NAME) .withAstyanaxConfiguration(new AstyanaxConfigurationImpl() .setDiscoveryType(NodeDiscoveryType.RING_DESCRIBE) ) @@ -79,15 +83,43 @@ public void init() { .withConnectionPoolMonitor(new CountingConnectionPoolMonitor()) .buildKeyspace(ThriftFamilyFactory.getInstance()); - context.start(); - keyspace = context.getEntity(); + keyspaceContext.start(); + + // Create keyspace if it doesn't already exist. + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + Keyspace ks = createKeyspaceIfNotExists(); + + keyspace = keyspaceContext.getEntity(); EMP_CF = ColumnFamily.newColumnFamily( EMP_CF_NAME, IntegerSerializer.get(), StringSerializer.get()); + + // Create column family if it doesn't already exist. + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + createColumnFamilyIfNotExists(); } - + + private Keyspace createKeyspaceIfNotExists() { + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + Keyspace ks = null; + try { + ks = keyspaceContext.getClient(); + + Properties props = new Properties(); + props.setProperty("name", KEYSPACE_NAME); + props.setProperty("strategy_class", "SimpleStrategy"); + props.setProperty("strategy_options.replication_factor", "1"); + + ks.createKeyspaceIfNotExists(props); + KeyspaceDefinition ksDef = ks.describeKeyspace(); + } catch (Exception e) { + logger.info("Didn't (re)create keyspace, message={}", e.getMessage()); + } + return ks; + } + public void insert(int empId, int deptId, String firstName, String lastName) { try { @SuppressWarnings("unused") @@ -126,7 +158,8 @@ public void insertDynamicProperties(int id, String[] ... entries) { } - public void createCF() { + public void createColumnFamilyIfNotExists() { + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. logger.debug("CQL: "+CREATE_STATEMENT); try { @SuppressWarnings("unused") @@ -134,9 +167,8 @@ public void createCF() { .prepareQuery(EMP_CF) .withCql(CREATE_STATEMENT) .execute(); - } catch (ConnectionException e) { - logger.error("failed to create CF", e); - throw new RuntimeException("failed to create CF", e); + } catch (Exception e) { + logger.info("Didn't (re)create column family, message={}", e.getMessage()); } } diff --git a/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstClient.java b/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstClient.java index 785ec7c5b..9a28ce324 100644 --- a/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstClient.java +++ b/astyanax-examples/src/main/java/com/netflix/astyanax/examples/AstClient.java @@ -18,7 +18,9 @@ import static com.netflix.astyanax.examples.ModelConstants.*; import java.util.Iterator; +import java.util.Properties; +import com.netflix.astyanax.ddl.KeyspaceDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,17 +50,18 @@ public class AstClient { private static final Logger logger = LoggerFactory.getLogger(AstClient.class); - private AstyanaxContext context; + private AstyanaxContext keyspaceContext; private Keyspace keyspace; private ColumnFamily EMP_CF; + private static final String KEYSPACE_NAME = "test1"; private static final String EMP_CF_NAME = "employees2"; public void init() { logger.debug("init()"); - - context = new AstyanaxContext.Builder() + + keyspaceContext = new AstyanaxContext.Builder() .forCluster("Test Cluster") - .forKeyspace("test1") + .forKeyspace(KEYSPACE_NAME) .withAstyanaxConfiguration(new AstyanaxConfigurationImpl() .setDiscoveryType(NodeDiscoveryType.RING_DESCRIBE) ) @@ -73,15 +76,54 @@ public void init() { .withConnectionPoolMonitor(new CountingConnectionPoolMonitor()) .buildKeyspace(ThriftFamilyFactory.getInstance()); - context.start(); - keyspace = context.getEntity(); - + keyspaceContext.start(); + + // Create keyspace if it doesn't already exist. + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + Keyspace ks = createKeyspaceIfNotExists(); + + keyspace = keyspaceContext.getEntity(); + EMP_CF = ColumnFamily.newColumnFamily( EMP_CF_NAME, IntegerSerializer.get(), StringSerializer.get()); + + // Create column family if it doesn't already exist. + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + createColumnFamilyIfNotExists(ks); } - + + private Keyspace createKeyspaceIfNotExists() { + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + Keyspace ks = null; + try { + ks = keyspaceContext.getClient(); + + Properties props = new Properties(); + props.setProperty("name", KEYSPACE_NAME); + props.setProperty("strategy_class", "SimpleStrategy"); + props.setProperty("strategy_options.replication_factor", "1"); + + ks.createKeyspaceIfNotExists(props); + KeyspaceDefinition ksDef = ks.describeKeyspace(); + } catch (Exception e) { + logger.info("Didn't (re)create keyspace, message={}", e.getMessage()); + } + return ks; + } + + private void createColumnFamilyIfNotExists(Keyspace ks) { + // Don't do in production; better to create from cqlsh to avoid parallel issues from eventual consistency. + if(ks != null) { + try { + ks.createColumnFamily(EMP_CF, null); + } catch (Exception e) { + // Do nothing + } + } + } + public void insert(int empId, int deptId, String firstName, String lastName) { MutationBatch m = keyspace.prepareMutationBatch(); @@ -101,10 +143,7 @@ public void insert(int empId, int deptId, String firstName, String lastName) { } logger.debug("insert ok"); } - - public void createCF() { - } - + public void read(int empId) { OperationResult> result; try { diff --git a/astyanax-queue/build.gradle b/astyanax-queue/build.gradle index 32a62d13a..22e53e171 100644 --- a/astyanax-queue/build.gradle +++ b/astyanax-queue/build.gradle @@ -1,17 +1,13 @@ apply plugin: 'osgi' +/** + * Queue implementation backed by Cassandra storage. + * Use at your own risk -- Cassandra can be a *very bad* storage engine for queues. + * If you insist on using Cassandra for queues, set a very short TTL and use DateTieredCompactionStrategy (DTCS). + */ dependencies { compile project(':astyanax-core') compile project(':astyanax-cassandra') runtime project(':astyanax-thrift') compile project(':astyanax-recipes') - compile ("com.google.guava:guava:$guavaVersion") { - force = true - } - compile "org.slf4j:slf4j-api:$slf4jVersion" - compile "commons-lang:commons-lang:$commonsLangVersion" - compile "org.codehaus.jackson:jackson-core-asl:$jacksonVersion" - compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion" - testCompile "junit:junit:$junitVersion" - testCompile project(':astyanax-thrift') } diff --git a/astyanax-queue/src/main/java/com/netflix/astyanax/recipes/queue/ShardedDistributedMessageQueue.java b/astyanax-queue/src/main/java/com/netflix/astyanax/recipes/queue/ShardedDistributedMessageQueue.java index 90c150a22..3f876ae75 100644 --- a/astyanax-queue/src/main/java/com/netflix/astyanax/recipes/queue/ShardedDistributedMessageQueue.java +++ b/astyanax-queue/src/main/java/com/netflix/astyanax/recipes/queue/ShardedDistributedMessageQueue.java @@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import org.apache.cassandra.thrift.SchemaDisagreementException; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonParseException; @@ -424,7 +423,8 @@ String getShardKey(Message message) { /** * Return the shard for this timestamp - * @param message + * @param messageTime + * @param modShard * @return */ private String getShardKey(long messageTime, int modShard) { @@ -731,14 +731,17 @@ private void changeSchema(Callable callable) throws MessageQueueException throw new MessageQueueException("Interrupted while trying to create column family for queue " + getName(), ie); } return; - } catch (SchemaDisagreementException e) { - try { - Thread.sleep(SCHEMA_CHANGE_DELAY); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw new MessageQueueException("Interrupted while trying to create column family for queue " + getName(), ie); - } } catch (Exception e) { + if (e.getClass().getSimpleName().equals("SchemaDisagreementException")){ + // Check by class name since SchemaDisagreementException is defined in cassandra-thrift, + // but astayanx-cassandra should not have a Thrift-specific dependency. + try { + Thread.sleep(SCHEMA_CHANGE_DELAY); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new MessageQueueException("Interrupted while trying to create column family for queue " + getName(), ie); + } + } if (e.getMessage().contains("already exist")) return; throw new MessageQueueException("Failed to create column family for " + queueColumnFamily.getName(), e); @@ -1032,7 +1035,7 @@ public List peekMessages(int itemsToPeek) throws MessageQueueException * Peek into messages contained in the shard. This call does not take trigger time into account * and will return messages that are not yet due to be executed * @param shardName - * @param itemsToPop + * @param itemsToPeek * @return * @throws MessageQueueException */ diff --git a/astyanax-recipes/build.gradle b/astyanax-recipes/build.gradle index 67dfa8d42..81db1448d 100644 --- a/astyanax-recipes/build.gradle +++ b/astyanax-recipes/build.gradle @@ -1,11 +1,10 @@ +/** + * Optional implementations of some common patterns. + * Use at your own risk; some of these are popular but not well-suite for Cassandra for many use cases (looking at you, AllRowsReader). + */ dependencies { - compile project(':astyanax-core') - compile project(':astyanax-cassandra') - compile ("com.google.guava:guava:$guavaVersion") { - force = true - } - compile "org.slf4j:slf4j-api:$slf4jVersion" + compile project(':astyanax-core') + compile project(':astyanax-cassandra') - testCompile "junit:junit:$junitVersion" testCompile project(':astyanax-thrift') } diff --git a/astyanax-test/build.gradle b/astyanax-test/build.gradle index 9c4454e00..714400459 100644 --- a/astyanax-test/build.gradle +++ b/astyanax-test/build.gradle @@ -1,24 +1,25 @@ apply plugin: 'osgi' +/** + * Unit/integration tests for all of Astyanax. + */ dependencies { compile project(':astyanax-core') compile project(':astyanax-cassandra') + compile project(':astyanax-thrift') compile project(':astyanax-cql') - compile project(':astyanax-contrib') - compile project(':astyanax-recipes') - compile project(':astyanax-queue') - compile project(':astyanax-entity-mapper') - compile "junit:junit:$junitVersion" - compile "joda-time:joda-time:$jodaTimeVersion" - compile "javax.persistence:persistence-api:$jpaVersion" - compile "org.slf4j:slf4j-api:$slf4jVersion" - compile "commons-lang:commons-lang:$commonsLangVersion" - compile("com.datastax.cassandra:cassandra-driver-core:$javaDriverVersion") { - exclude group: 'com.google.guava', module: 'guava' - } - compile ("org.apache.cassandra:cassandra-all:$cassandraVersion") { - force = true - } + compile project(path: ':astyanax-contrib') + compile project(path: ':astyanax-recipes') + compile project(path: ':astyanax-queue') + compile project(path: ':astyanax-entity-mapper') + compile "org.slf4j:log4j-over-slf4j:$slf4jVersion" + + // CassandraDaemon for EmbeddedCassandra + compile "org.cassandraunit:cassandra-unit-shaded:$cassandraUnitShadedVersion" + + testCompile "junit:junit:$junitVersion" + testCompile "org.slf4j:slf4j-api:$slf4jVersion" + testCompile "org.slf4j:slf4j-simple:$slf4jVersion" } diff --git a/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/SerializerPackageTests.java b/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/SerializerPackageTests.java index 09517f5c9..01bc50173 100644 --- a/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/SerializerPackageTests.java +++ b/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/SerializerPackageTests.java @@ -19,9 +19,11 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.CompositeType; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.Serializer; +import com.netflix.astyanax.serializers.SpecificCompositeSerializer; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.CompositeType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import org.apache.log4j.Logger; import org.junit.AfterClass; import org.junit.Assert; @@ -32,7 +34,6 @@ import com.netflix.astyanax.model.ColumnFamily; import com.netflix.astyanax.model.Composite; import com.netflix.astyanax.serializers.LongSerializer; -import com.netflix.astyanax.serializers.SpecificCompositeSerializer; import com.netflix.astyanax.serializers.StringSerializer; public class SerializerPackageTests extends KeyspaceTests { @@ -84,12 +85,13 @@ public void testSerializer() throws Exception { System.out.println("ss1Result: " + ss1Result); Assert.assertEquals(ss1, ss1Result); - SpecificCompositeSerializer comp = (SpecificCompositeSerializer) serializer.getColumnNameSerializer(); - System.out.println(comp.getComparators().toString()); + Serializer comp = serializer.getColumnNameSerializer(); + System.out.println(comp.getComparatorType().toString()); Composite dc = new Composite(ss1); - List> types = new ArrayList>(); + List> types = + new ArrayList>(); types.add(UTF8Type.instance); CompositeType c1 = CompositeType.getInstance(types); diff --git a/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/utils/AstyanaxContextFactory.java b/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/utils/AstyanaxContextFactory.java index d814fdab6..3c29b0e43 100644 --- a/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/utils/AstyanaxContextFactory.java +++ b/astyanax-test/src/main/java/com/netflix/astyanax/cql/test/utils/AstyanaxContextFactory.java @@ -15,23 +15,8 @@ */ package com.netflix.astyanax.cql.test.utils; -import static com.netflix.astyanax.cql.test.utils.ClusterConfiguration.TEST_CLUSTER_NAME; -import static com.netflix.astyanax.cql.test.utils.ClusterConfiguration.TEST_KEYSPACE_NAME; -import static com.netflix.astyanax.cql.test.utils.ClusterConfiguration.TheDriver; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.log4j.PropertyConfigurator; - import com.datastax.driver.core.Configuration; -import com.datastax.driver.core.MetricsOptions; -import com.datastax.driver.core.PoolingOptions; import com.datastax.driver.core.ProtocolOptions; -import com.datastax.driver.core.QueryOptions; -import com.datastax.driver.core.SocketOptions; -import com.datastax.driver.core.policies.Policies; import com.google.common.base.Supplier; import com.netflix.astyanax.AstyanaxContext; import com.netflix.astyanax.Cluster; @@ -43,9 +28,16 @@ import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor; import com.netflix.astyanax.cql.CqlFamilyFactory; import com.netflix.astyanax.cql.JavaDriverConnectionPoolConfigurationImpl; -import com.netflix.astyanax.cql.test.utils.ClusterConfiguration.Driver; +import com.netflix.astyanax.cql.test.utils.ClusterConfiguration.*; import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; import com.netflix.astyanax.thrift.ThriftFamilyFactory; +import org.apache.log4j.PropertyConfigurator; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static com.netflix.astyanax.cql.test.utils.ClusterConfiguration.*; public class AstyanaxContextFactory { @@ -174,12 +166,9 @@ public List get() { ProtocolOptions protocolOptions = new ProtocolOptions(9042); - Configuration jdConfig = new Configuration(new Policies(), - protocolOptions, - new PoolingOptions(), - new SocketOptions(), - new MetricsOptions(), - new QueryOptions()); + Configuration jdConfig = Configuration.builder() + .withProtocolOptions(protocolOptions) + .build(); AstyanaxContext context = new AstyanaxContext.Builder() .forKeyspace(keyspaceName) diff --git a/astyanax-test/src/main/resources/cassandra2-template.yaml b/astyanax-test/src/main/resources/cassandra2-template.yaml index fe88ac21f..a14107f4c 100644 --- a/astyanax-test/src/main/resources/cassandra2-template.yaml +++ b/astyanax-test/src/main/resources/cassandra2-template.yaml @@ -28,7 +28,7 @@ seed_provider: - seeds: 127.0.0.1 concurrent_reads: 32 concurrent_writes: 64 -memtable_flush_queue_size: 4 +#memtable_flush_queue_size: 4 trickle_fsync: false trickle_fsync_interval_in_kb: 10240 storage_port: $STORAGE_PORT$ @@ -45,10 +45,10 @@ incremental_backups: false snapshot_before_compaction: false auto_snapshot: false column_index_size_in_kb: 64 -in_memory_compaction_limit_in_mb: 128 -multithreaded_compaction: false +#in_memory_compaction_limit_in_mb: 128 +#multithreaded_compaction: false compaction_throughput_mb_per_sec: 128 -compaction_preheat_key_cache: true +#compaction_preheat_key_cache: true read_request_timeout_in_ms: 10000 range_request_timeout_in_ms: 10000 write_request_timeout_in_ms: 10000 @@ -62,7 +62,7 @@ dynamic_snitch_badness_threshold: 0.1 request_scheduler: org.apache.cassandra.scheduler.NoScheduler index_interval: 256 inter_dc_tcp_nodelay: true -memtable_total_space_in_mb: 1024 +#memtable_total_space_in_mb: 1024 rpc_min_threads: 16 rpc_max_threads: 2048 stream_throughput_outbound_megabits_per_sec: '400' @@ -70,6 +70,6 @@ dynamic_snitch: true concurrent_compactors: 1 num_tokens: 1 auto_bootstrap: true -preheat_kernel_page_cache: false +#preheat_kernel_page_cache: false cas_contention_timeout_in_ms: 1000 file_cache_size_in_mb: '512' \ No newline at end of file diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/CompositeEntityManagerTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/CompositeEntityManagerTest.java index a85eab320..116e39a57 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/CompositeEntityManagerTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/CompositeEntityManagerTest.java @@ -119,7 +119,7 @@ private static void createKeyspace() throws Exception { keyspace.dropKeyspace(); } catch (Exception e) { - LOG.info(e.getMessage(), e); + LOG.info(e.getMessage()); } keyspace.createKeyspace(ImmutableMap.builder() diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/SampleEntity.java b/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/SampleEntity.java index 1ecb0d988..93cb83ea7 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/SampleEntity.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/entitystore/SampleEntity.java @@ -11,7 +11,7 @@ import javax.persistence.Entity; import javax.persistence.Id; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.codehaus.jettison.json.JSONException; diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/recipes/LockRecipeTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/recipes/LockRecipeTest.java index 89ff1a4c2..714ba19c0 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/recipes/LockRecipeTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/recipes/LockRecipeTest.java @@ -2,6 +2,7 @@ import java.util.concurrent.TimeUnit; +import com.netflix.astyanax.test.EmbeddedCassandra; import junit.framework.Assert; import org.junit.AfterClass; @@ -24,7 +25,6 @@ import com.netflix.astyanax.recipes.locks.StaleLockException; import com.netflix.astyanax.serializers.LongSerializer; import com.netflix.astyanax.serializers.StringSerializer; -import com.netflix.astyanax.test.EmbeddedCassandra; import com.netflix.astyanax.thrift.ThriftFamilyFactory; /** diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/recipes/MiscUnitTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/recipes/MiscUnitTest.java index f82f6b61d..be134bcdf 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/recipes/MiscUnitTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/recipes/MiscUnitTest.java @@ -1,27 +1,9 @@ package com.netflix.astyanax.recipes; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -import junit.framework.Assert; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.common.base.Function; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.netflix.astyanax.AstyanaxContext; import com.netflix.astyanax.ColumnListMutation; import com.netflix.astyanax.Keyspace; @@ -33,13 +15,7 @@ import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor; import com.netflix.astyanax.ddl.KeyspaceDefinition; import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; -import com.netflix.astyanax.model.Column; -import com.netflix.astyanax.model.ColumnFamily; -import com.netflix.astyanax.model.ColumnList; -import com.netflix.astyanax.model.ConsistencyLevel; -import com.netflix.astyanax.model.Row; -import com.netflix.astyanax.partitioner.Murmur3Partitioner; -import com.netflix.astyanax.recipes.UUIDStringSupplier; +import com.netflix.astyanax.model.*; import com.netflix.astyanax.recipes.functions.ColumnCounterFunction; import com.netflix.astyanax.recipes.functions.RowCopierFunction; import com.netflix.astyanax.recipes.functions.RowCounterFunction; @@ -47,11 +23,7 @@ import com.netflix.astyanax.recipes.locks.ColumnPrefixDistributedRowLock; import com.netflix.astyanax.recipes.locks.StaleLockException; import com.netflix.astyanax.recipes.reader.AllRowsReader; -import com.netflix.astyanax.recipes.uniqueness.ColumnPrefixUniquenessConstraint; -import com.netflix.astyanax.recipes.uniqueness.DedicatedMultiRowUniquenessConstraint; -import com.netflix.astyanax.recipes.uniqueness.MultiRowUniquenessConstraint; -import com.netflix.astyanax.recipes.uniqueness.NotUniqueException; -import com.netflix.astyanax.recipes.uniqueness.RowUniquenessConstraint; +import com.netflix.astyanax.recipes.uniqueness.*; import com.netflix.astyanax.serializers.IntegerSerializer; import com.netflix.astyanax.serializers.LongSerializer; import com.netflix.astyanax.serializers.StringSerializer; @@ -59,6 +31,19 @@ import com.netflix.astyanax.thrift.ThriftFamilyFactory; import com.netflix.astyanax.util.SingletonEmbeddedCassandra; import com.netflix.astyanax.util.TimeUUIDUtils; +import junit.framework.Assert; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; public class MiscUnitTest { private static Logger LOG = LoggerFactory.getLogger(MiscUnitTest.class); @@ -959,7 +944,7 @@ public synchronized Boolean apply(Row row) { .build(); try { - Stopwatch sw = new Stopwatch().start(); + Stopwatch sw = Stopwatch.createStarted(); boolean result = reader.call(); long runtimeMillis = sw.stop().elapsed(TimeUnit.MILLISECONDS); diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/serializers/SerializersTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/serializers/SerializersTest.java index 7cc2d87f3..5c1271b97 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/serializers/SerializersTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/serializers/SerializersTest.java @@ -9,10 +9,10 @@ import junit.framework.Assert; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.CompositeType; -import org.apache.cassandra.db.marshal.TypeParser; -import org.apache.cassandra.utils.ByteBufferUtil; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.AbstractType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.CompositeType; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.ShadedTypeParser; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.ByteBufferUtil; import org.apache.commons.lang.StringUtils; import org.junit.Test; import org.slf4j.Logger; @@ -714,7 +714,7 @@ public void testCompositeType() { String comparatorType = "CompositeType(UTF8Type,UTF8Type)"; String columnName = "(abc,1234)"; try { - AbstractType type = TypeParser.parse(comparatorType); + AbstractType type = ShadedTypeParser.parse(comparatorType); if (type instanceof CompositeType) { CompositeType ctype = (CompositeType) type; @@ -734,7 +734,7 @@ public void testCompositeType() { public void testDeserializeOfSepecificSerializer() throws Exception { Composite composite1 = new Composite("abc", 123L); CompositeSerializer serializer = new SpecificCompositeSerializer( - (CompositeType) TypeParser + (CompositeType) ShadedTypeParser .parse("CompositeType(UTF8Type,LongType)")); ByteBuffer byteBuffer = serializer.toByteBuffer(composite1); Composite composite2 = serializer.fromByteBuffer(byteBuffer); diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/thrift/CqlTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/thrift/CqlTest.java index 58bd434f0..f4412e0b8 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/thrift/CqlTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/thrift/CqlTest.java @@ -7,7 +7,7 @@ import junit.framework.Assert; -import org.apache.cassandra.db.marshal.UTF8Type; +import com.netflix.astyanax.shaded.org.apache.cassandra.db.marshal.UTF8Type; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; diff --git a/astyanax-test/src/test/java/com/netflix/astyanax/thrift/ThriftKeyspaceImplTest.java b/astyanax-test/src/test/java/com/netflix/astyanax/thrift/ThriftKeyspaceImplTest.java index b0b1f386f..0ed14c809 100644 --- a/astyanax-test/src/test/java/com/netflix/astyanax/thrift/ThriftKeyspaceImplTest.java +++ b/astyanax-test/src/test/java/com/netflix/astyanax/thrift/ThriftKeyspaceImplTest.java @@ -25,7 +25,7 @@ import junit.framework.Assert; import org.apache.cassandra.thrift.KsDef; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import org.apache.commons.lang.RandomStringUtils; import org.apache.log4j.BasicConfigurator; import org.junit.AfterClass; @@ -2219,7 +2219,7 @@ public void testGetAllKeysRoot() { Assert.assertNull(row); row = result.getResult().getRowByIndex(10); - Assert.assertEquals("M", row.getKey()); + Assert.assertEquals("K", row.getKey()); /* * LOG.info("Get " + result.getResult().size() + " keys"); for * (Row row : result.getResult()) { diff --git a/astyanax-thrift/build.gradle b/astyanax-thrift/build.gradle index 5aeefd570..3f3fc8b41 100644 --- a/astyanax-thrift/build.gradle +++ b/astyanax-thrift/build.gradle @@ -1,12 +1,13 @@ apply plugin: 'osgi' +/** + * Thrift protocol implementation of astyanax-core. + * Note Apache Cassandra 4.x+ will drop support for Thrift protocol. + * Switch to DataStax Java Driver for Apache Cassandra + * or astyanax-cql. + */ dependencies { compile project(':astyanax-core') - compile project(':astyanax-cassandra') + compile project(':astyanax-cassandra') compile "org.apache.cassandra:cassandra-thrift:$cassandraVersion" - compile ("org.apache.thrift:libthrift:$thriftVersion") { - force = true - } - - testCompile "junit:junit:$junitVersion" } diff --git a/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/ThriftAllRowsQueryImpl.java b/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/ThriftAllRowsQueryImpl.java index 2c9d81d52..a25e183e9 100644 --- a/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/ThriftAllRowsQueryImpl.java +++ b/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/ThriftAllRowsQueryImpl.java @@ -32,7 +32,7 @@ import org.apache.cassandra.thrift.SlicePredicate; import org.apache.cassandra.thrift.SliceRange; import org.apache.cassandra.thrift.Cassandra.Client; -import org.apache.cassandra.utils.Pair; +import com.netflix.astyanax.shaded.org.apache.cassandra.utils.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/model/ThriftCounterColumnListImpl.java b/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/model/ThriftCounterColumnListImpl.java index a4df86ad1..ff5abb89a 100644 --- a/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/model/ThriftCounterColumnListImpl.java +++ b/astyanax-thrift/src/main/java/com/netflix/astyanax/thrift/model/ThriftCounterColumnListImpl.java @@ -24,13 +24,14 @@ import com.netflix.astyanax.Serializer; import com.netflix.astyanax.model.AbstractColumnList; import com.netflix.astyanax.model.Column; +import org.apache.cassandra.thrift.CounterColumn; public class ThriftCounterColumnListImpl extends AbstractColumnList { - private final List columns; - private Map lookup; + private final List columns; + private Map lookup; private final Serializer colSer; - public ThriftCounterColumnListImpl(List columns, Serializer colSer) { + public ThriftCounterColumnListImpl(List columns, Serializer colSer) { this.columns = columns; this.colSer = colSer; } @@ -38,9 +39,9 @@ public ThriftCounterColumnListImpl(List> iterator() { class IteratorImpl implements Iterator> { - Iterator base; + Iterator base; - public IteratorImpl(Iterator base) { + public IteratorImpl(Iterator base) { this.base = base; } @@ -51,7 +52,7 @@ public boolean hasNext() { @Override public Column next() { - org.apache.cassandra.thrift.CounterColumn c = base.next(); + CounterColumn c = base.next(); return new ThriftCounterColumnImpl(colSer.fromBytes(c.getName()), c); } @@ -67,7 +68,7 @@ public void remove() { public Column getColumnByName(C columnName) { constructMap(); - org.apache.cassandra.thrift.CounterColumn c = lookup.get(columnName); + CounterColumn c = lookup.get(columnName); if (c == null) { return null; } @@ -76,7 +77,7 @@ public Column getColumnByName(C columnName) { @Override public Column getColumnByIndex(int idx) { - org.apache.cassandra.thrift.CounterColumn c = columns.get(idx); + CounterColumn c = columns.get(idx); return new ThriftCounterColumnImpl(colSer.fromBytes(c.getName()), c); } @@ -114,7 +115,7 @@ public Collection getColumnNames() { private void constructMap() { if (lookup == null) { lookup = Maps.newHashMap(); - for (org.apache.cassandra.thrift.CounterColumn column : columns) { + for (CounterColumn column : columns) { lookup.put(colSer.fromBytes(column.getName()), column); } } diff --git a/astyanax/build.gradle b/astyanax/build.gradle index 9a300749e..71a64a22b 100644 --- a/astyanax/build.gradle +++ b/astyanax/build.gradle @@ -1,6 +1,15 @@ +/** + * Project to add dependencies on every Astyanax module. + * + * Please don't depend on this module; it's a bad practice and exists only for backwards-compatibility. + * + * Instead add explicit dependency on only the modules you actually use, for example astyanax-thrift or astyanax-cql. + * Depending on these will automatically add any other necessary transitive dependencies. + */ dependencies { compile project(':astyanax-core') compile project(':astyanax-cassandra') + compile project(':astyanax-cassandra-all-shaded') compile project(':astyanax-thrift') compile project(':astyanax-cql') compile project(':astyanax-queue') diff --git a/build.gradle b/build.gradle index 16043724a..e0bac0b0f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,11 @@ plugins { - id 'nebula.netflixoss' version '2.2.5' + id 'nebula.netflixoss' version '4.0.1' } ext.githubProjectName = rootProject.name // Change if github project name is not the same as the root project's name apply from: 'dependency-versions.gradle' +print "Using cassandraVersion=${cassandraVersion}\n" subprojects { apply plugin: 'nebula.netflixoss' @@ -12,10 +13,14 @@ subprojects { group = "com.netflix.${githubProjectName}" - sourceCompatibility = 1.6 - targetCompatibility = 1.6 - repositories { jcenter() } + + // Don't stop the build for JavaDoc errors + javadoc{ + options { + failOnError = false + } + } } diff --git a/dependency-versions.gradle b/dependency-versions.gradle index bd0d94982..fa5d14341 100644 --- a/dependency-versions.gradle +++ b/dependency-versions.gradle @@ -1,26 +1,38 @@ ext { - cassandraVersion = "2.0.12" - thriftVersion = "0.9.2" - junitVersion = "4.8.1" - guavaVersion = "15.0" - snappyVersion = "1.1.1.6" + cassandraVersion = "2.0.17" + + // DataStax cassandra-driver-core version for astyanax-cql: + javaDriverVersion = "3.3.2" + + // EmbeddedCassandra for astyanax-test + cassandraUnitShadedVersion="2.1.9.2" + + thriftVersion = "0.10.0" + junitVersion = "4.12" + + // Depend on Guava version 19 for minimum required backwards compatibility + // but test against 20, 21, 22, 23.6-jre for forwards-compatibility to avoid forcing anyone to 19. + guavaVersion = "19.0" +// guavaVersion = "20.0" +// guavaVersion = "21.0" +// guavaVersion = "22.0" +// guavaVersion = "23.6-jre" +// guavaVersion = "24.1.1-jre" +// guavaVersion = "25.0-jre" + jacksonVersion = "1.9.13" - cassandraThriftVersion = "2.0.12" - jodaTimeVersion = "1.6.2" - highScaleLibVersion = "1.1.2" + jodaTimeVersion = "2.9.9" + highScaleLibVersion = "1.1.4" uuidVersion = "3.2" - slf4jVersion = "1.6.4" - jettisonVersion = "1.2" - commonsCodecVersion = "1.5" - commonsLangVersion = "2.4" + slf4jVersion = "1.7.25" + jettisonVersion = "1.3.8" + commonsCodecVersion = "1.10" + commonsLangVersion = "2.6" commonsCsvVersion = "1.0-r706900_3" - concurrentLinkedHashMapVersion = "1.3" - jacksonVersion = "1.9.2" - snappyVersion = "1.0.4.1" - jpaVersion = "1.0" - nettyVersion = "3.9.0.Final" - commonsIOVersion = "2.1" - javaDriverVersion = "2.0.6" - lz4Version = "1.2.0" + jpaVersion = "1.0.2" + // LZ4 version suggested by cassandra-driver-core 3.3.2: + lz4Version = "1.3.0" + // Snappy version suggested by cassandra-driver-core 3.3.2: + snappyVersion = "1.1.2.6" metricCoreVersion = "3.0.2" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c97a8bdb9088d370da7e88784a7a093b971aa23a..6ffa237849ef3607e39c3b334a92a65367962071 100644 GIT binary patch delta 31028 zcmY(qQ*b6s*998ePA0Z(+qP}n$&-m~+qRvFHL-2mPR{)Axj5&$=-RugyDz$G)!wUn zt)7Sl{gVTVpeO?h1_J~H1qF0%&LWk7KnDMRv9(SpP5=l9NHsxF8RG)`-uccM80i07 zy8C}iD+$5=i_;YUZ+v6;FDB6f?;`)-#PD{=G8zaFkRuonkZ4i?Ek@FxZm1;2cB&*( z5IjIk1T>RerJSsQoe`)-t%s>Y!hPLilpX6G&tAhS(T1b`{O#Ydln)b{k@Z(Lb%fec;A1^>-`uLfch%O(hox zN)0tDeSUi`@Nr?>(+NCN8LtdNydZWwKKcXDWXoGInW_mMNyy}%%~p|>y@-i#nhmZh zTcMIycBzx$mM;jA8jl|+QHH#<5X~m-q_!VJHj=tUR6Q6j!TrawvL0a7u~Kz}uT(np zDYHz{F(Kf7$v1re1{~TGCiC!B81M3fO7^m-$aEA!qVjARQT~-OWkejPwGG$E#a93v zgHT&hVK8eJWQ8VotBM@6%k}N@lH{=d!rI@2WqVH|_Y?FTZ^${**1IIJCIMUjkO)y`}pPFG^rZxwP0x3cL_Wx9w<6b>QY8|KX_)PX3t@0tl!D76^#tKRoe5 z5d+3u(A03h^_f|6WVxY5!x6yFfF?+h#B~KJkl{(@enUr)f`LG}o8wC*$$MO$5e?y? zZ+htFSKk|_9LZI&22=!Qse3h6-!t5f4M(ZzqxG%i1ltbn->2?;(Cs)}eIM-*)B&wV zJ~-J5YLG1#>Ch=yuVPAo@!@zMgcr!@Jp-y#48Re}zw!$otB&He5vq>DL(8ZgRq^76 z8{ia1jf@3FdO<=s)v2FT;+=3u4YS2V_v;-CK?bGs^nNE$b2=Vi4YR1-DlYEYP+D

9}mJW(LTG>R+Z{UB_8w5=uFGzT&aB`gqeTMv);jovD*au*RzktdP)+2&z>?t zTUDPoyRBX_a!amNhQb9*m1L+-yn`x)W@uNL*^V=cisogmxKr9>#4d_2>uuL^XC{ps z7F_=dogrKPg@XdQ8lOd+l%wihz6Ge?)A6KuN zZu<6k$23X8y7J57oOgk$ox})8xK)j%^?AxiWm>i%hpFdJkgO+>!`EhZbR0nb#B6ZB ze8byE$3u~S8h2c2$9S$@-Md<7^ic-?h-brl0hXlaAk`Ti$7+A#PjvbLD6>hI*qiBi zV(-+S#hqCU-rzSg=#lV2HA?mAK*pftb?S$OPmngc!r<7ab}|Nml=T*lx*Y5;C_A}z)d*~zj=;DC%?rU49gUyeUX?5w`U2PWAIw-K=lOr?7)czGt#Qre_(Aarf4 ztgBKFu*&(o6)>gVitk%O^}-ryWLd`^P2#CN$4~?sq*)kQ*-U0TR7h&hdk?}96_-`d zFI(}X_5O3;=|z$$FJk~k6>Ydb1p8{ajgkCyYU)nQtL0?7&%X<8(tOwK-bF4$hXbCb zpk-z}=h8@Q+bZ-`xvrXRH(+XvG$(xspd*kx8M)K zj6uJH#QmE&ZaoTcp*KUii0TvCn1__gN+D<5tOLMs17kWeY+xO}4yjJ`H++!`y`B&$ zZR}rxk9NQZbU+%QR>W&B^8E#@Ey=|qkt6r;zY7RCsaS|PsYJ|pccMPV#8}KgKV!-v zhFYaW-#O$6xg!9YKVp`=>azKb^wVY_YM2E#c6&D5iQ%&O?;x#}J;%J2i~2smF-e-s z0%SUE*}{jlS*bl&m68%--0sjctv1AZ8RehYc-@OOQ#(bM(O%cEws}Jn->IGNQSE$q za-tND65Ofba)ii^20v^}gIctk- zwbp576~prtreD+qam-5+8Nf;Z%I<`*yzS=9*$)up6?^Q^ReM0#E!q4lOwB8V-D|FL z+MIk;mm?r1=~cn>4%7X>;j&*~jA>YpIMTk*Q@2`BowQzu)iBQTa6`P;PRK)%NJ5hV zqjq*3bk@=>@wqi=ggU(zCl|li4LN(h*&x(yaZOyCE3ps)@d1tKeWw95j!>GwpQO`W zeL+6*f+Z1KnqOQxIN5Eerm`()c;;qIwSeGQA43`KU7_5*H(LC69rC!JgmZB85*jRQ zZ?1X3S1~@}7m@)&<7pfi{Zdj{s88x1l#Cyu^8}(R1o_K1P5*gZKjj3JZy^h_dr{S)dWsnAG6VC4EI|9aBk;VmI~J?o_OZ zneIXe{veuV_vqwTZwzs;G_gG7eO>8(e9X)k03F{_hRLLl8?r~?M#Eh@35-tha=dhS z{h1QRiL&A~oqe_wxV=>8sMiZKs&}&VXNDgIz(m(fJ(aA;ngmzi+e6-lv` z(Y^Gr1c8PYY^y%}$u`imb&tcEH@G~oM2RchUS!#52qFvhY-yc}*K~WS$aPqaeQNbt zZ0vq9uR`6vy3WY4TWEUIB1P^0eZ{hfQ@g^xp4+k05IvLY!0EZfV&MHa5SeU@RfZv0hScD257;L+jc^bPFk>)6h_bf(^q zud8A>+~9-8Sm3h#iN0y>*wErv6!%L3e`7*{;l;K&pYjY+YPk*e#V=;pVHttH!+?~- z?;_BKU#|)npbXG@NqxBE*W#&DQCvwE(HEODqn&*^(1o&F-NNyWq)H#FeKrL zC)$ETM>;1{873DpMJuXDK`5C=^A4ixqNiaUdeKz?B#tlE>XVVOqmnH4opezF`()>_ zclLhoU>PpSy6HKDQ&Wxuj*DdEx00|s6OMQsXoODKrpnOFLI>#ovSM+Z9z>`g#?O5n z>G)ze<|43;8VIQgLYLx<8&G8=!X)YVYPiOHu#O^#@d?5>sfS`jNu>Z;Dj{X&F&8pP zHTc9tp;W1+dB|c?L2K#wW;iBgjlfgue@UZ!=8B)-1c)j3;^r6Z|H9VP`|nQTKZ4Eo zk5DoGM~c3X2mm@NI%;U&>{6UWxD!3Bk~+mL!8*zel4Q-)Xr$;MWKbpg=S11$VWBSj z<5Iv5Pw(0WxEAL@4hYkn(~D`0W6^#u5*0i0#D~ihrj5_*Hhw-=EqyI3^55?taRR{I z;SPSM!^MGA1m}!rgKn}k>397kF%EC}hU)Uf%3TG!b^ve}m}5W7fnrQ0E_%rZ%3(PI zRaz;}YUD}JGGrRfl>OAr!%|Ee{>_mts|KwqD-f)BE!S z#KVuWhZr#~S?hju$@70jDUVk}GoWBu<+RL4=436zWX#-tze5kQX4`<&{`nIb;4`tF z-{LjR_yn+Yx6x}$Yk5kh57Kg?Ybjg{&Blzer?4^w3q7IbL6%-!7neq~+tB8&*(g}G zADL^zu!}++on>jtlxmwoTSx6wIYZ|ppe(l0EWjoy34?@Ls5U7Ue$dXm=nfq>IbBZ; z=!5&kw4hbCODxOSFiiq5HS(sYEyEjRK6BYx@Bm;-HleQ3ZMjZxX>0B*9v~>Wjfnk@ zP5dH>wYP~SOA<)opU|?G6Y*LHi4hy3Z%UO_#~)0i=XyFrd13lxbVhmL!|_FMQgj4N zQ5mIc*D(?R&qQKmEx&9YeP-d}x1Zjdp3-vQ{X0CjOqG{1t+G8F6HGhqC1Z(M>bium z)))Y)yQ35Z{kyhIRa?BfiuiXijL3+6os#qK>w}@R#!?g8GqUO02^Bf>n8i?gSl#qi zyUFV<`EzrJiFx+Z3%**N8@*#^wPEsWsRZQ)a20aX3dsW!@YYB;TyQP)mx8L*5d;SD zd$c;5jZgLq zMK55}i%A!lY%ced=M+)TeJUD~pmA4OV+oL@7vua1RZ=$H-wdrAO}F}^)ajl)AWMS8 z5H_ZY##-|0%dMALzTZ!m-|5$52Wi`$nTLa#=J9oM4QFdnX19r>`8Dg*`J^qfsyhH9 zsJ0A(2^9fvGi_>A3xPYc)||hUd!%onOT=j{RSIY3!XTOxP#@e$dNXJ%9uUNATFzvL z5%gb(9}*dCDLoLn(>8>TB_sE!;6o3bGX%^5X;Iz;qp;aa{WEp(h@eMg+QPWt5;ME} zLpNTM0%m^OrxF3mbU=&&Lb32z1B?JAUE1EDEXmzAk;R&;K{k%`pbrVpP<(;`5s|M? zqGUGlXYnuzXUpA{Hb}QkI`qDeaeVKhFvS&wInl?8)?-|4tU|W1)&#$10qp651E3s` zxdhN`4A0;T)lRUHEq@OLi}g<~SAl`sxx)@z?-4$K#Fo5SuEUo@J&QBGKU@Gz^_Ooh zEN?i>w^NDu2uLR`fp?}*C)RlqL;^9MBP~xSgsH=Z6u9?s0|Kp!6N~U>Fs@joUlepk zC{2;btIhm`MTpo>kl#jc;B~ti(Z^D{#?Ab$DCmduMvq7y8S5ghsB045cHNO_x6D*a zjzKgMXJjqSu}+Ws7P|d5v>~hnJ1#4XGK@;J^8FtF__HstLD{pozR!cf{}!(jFUC&w ze?1on90-Wu|B4qt*1^KU%$eTA*2u*rB#!;eicUg&tgI8D!o(fonLIx=Po# z^ZA3_s5iJ@S2oD+v0dSpB+uT}TADdpX#&N=8QoDf5?rv@f~Pl!=Vs`9R8$lzzNysN zTI*6ajKpS_Q=oX2TS1^YtV*YM))8xTMwg(7b3ZP+)B{I@pqdw;1sXbH?E9hW=IzUS zlscl*5)uW#%x$BTCtW5t2{qQl6=6wUI}27mK-F*?dgM;-r?D$rM|d*F;j;@hoyq0K z5nvu#RsOa`5S7Kj#w|?{))r=o@o8t+6T(4Mk}L|B5az|$hdzSNVICa$`E+=<&@BRK zo2H4du8nM9czxlQ)8#LQ^^sNr??W9_-=2rj)dmA-ge^*bXPc~VOKO%5=j%4-q3`bv zFBn8nO-uB+hQRoJLC@x&t)U$h1H`i*;g+S$$GL@r{RNf3p~=(y3GY*?q1YvBNu=+Y zjaSK~k-28>yCYH|dF2$Xa#n?j413|ywcdD$^AbKoE&W1QncKmj&F1Uq1DaeMQ{`Lv z9FYM$z^`gR({H=Uh_QWXeY6EM!M*ZCAF!vJxH*I9($__kz}iLhCXdq}r_q9G9u}H9 zAsW#7B$8V}lm4r?xrn>M8ztMsxvdh~i!z>c-D&f1^VZxU^K6vG*z+K;-}RP(}<4fWfa7yg$JLX^46U-J(>J)@;9NI zDW6P^hn$N`Cx7iraB4{t!&D_vXl%vClOu`2!bb zU;SY_bc68yC??WdWi$hnBZkRabf!nOgkFr{6|-+~w4LaE9VXvjJIE&t5_emay%ZD% z>UVzV0*a2doz1nF6mp|2Q!SA;fK*y=?d3)|pTcsU*gZ;4H!pu%XK!maDa2r|VMRw%d;jN%iiqL&3KDRg z8O#{6(fYb%cCkSYlT_xoEU_SR;<(JzGkxXRTwGmsq)f`A3U}A_=ID$Gz;UJ#!?}%^ z$S3Tp*%rGQo^9wRd{X&GnT$KpLD$4)u%=t^;~eo?J<*j6uT}d8!UVIw=j);E>;nz^~jkhn6u`5k*<)ANHiO zeL{J#@kP(;olv||VUJtBW>kycxxn`!ou#!G?SAsqIX2wUp!Y2^ z25D`dZyu6R`xos2+Fo-acWhokzsvU&UYi2x5m(8l%toe!mz`q*^WUQ=qxb6qEhYNz zFf!C<7glrXG;2GoF5j9w3RS6&QPs!gZ0i|OOVw)Iq5D|Hhn}mV(+75LJ>Wg=GD9mx zeKj`S*f|8@*)~n?gQiEKG>lk&jc2t+0xUiCP#7cimUB%3-^PI@DU3Oy!`|D}Tg2%@ zI!cmxF^1+V_E;C)$cTMYljDQq{m0h4+lxyhJvbNAt<~q|M_hG@B>NK+G+6#R9^8~v zXNvY#MD0NqSvdBvB9q@aMsfE{5U>P;nv?3?eKl?O^=~>&eH5lWFSa?F z(|EXVzhf!^O>bb(3Awv%b&Af4IGV-E(--u7!AUD4;HIPmK;fiK-_sGMt!9nNHSeA# z7EXioTZw6NQ9VI%+y~>obedL%O`{wo$BD`s-TQ2EWzBUyQt%1TLhppd7`Wx0KO6Dz zx;yg%?MB(y{q#>RQJPNd34lzoEnd}Ja}5lyPDe-p?Lgo+CzbG0wF31PIs0*b@y^LU zwo$kQ2j@N6=+k?u@Do+J=VY-=(}D!To=60R#c$HDj3b;;zwiG1Iaoy7O2?8cF%Z!i zoIoA`5hxK3_JaD#ElKKiHx3jG{qd%P~O_R zmW0>id2;kd+DkX34C0g)S0Iv`6>NGIm2M)%LtH>Y3sJST6Vk55&p ze+AMbWJ4{!hgL=^%^CDeR1VJc%?4qf&<)`PAQp$$_e7$yWoKK7?gtS(_usIR-QuEp zY!9a1+buT0dvc;FWZoy1?*Sk3+S=VifN$U2W@qC@K!&z0vx?_P#KELwPfW#odYm zAm1`ZK^n44y2HsBG84Gpk!gKSHx9`VKW))LIgGCqgr!Zd5>ziOHL2Yd<^MBJxJ+1L3NDP9FZ|tKu1mtK)S|{QR%J11rV3-MaM)6$t2(D#;O# z0-y!!i+0o^K+(0fMmtD@G%#T!O6(QJfrA7u6~BrUf6GXCZt9U3eMxdXohVwP9VNZg ztEe-lUL9vlr=}z|OSx+8*ZN%0($cb|ZsTxq)zkCxyuI;3{Q@v|=g2Ze+GptdGC%go zKKaSM@iox@`dE;Nup|N?u$ji@3LmVDgU6F z*(S0kf$EN_6ldV<66a#%^R#Pbp20wZvZh#!eP+PS+&^3b%^1n;Er7b>%)sAb^D1~t z7jvoXy#Z$q9)Rjjm;qNFIKNfLmnvsgI_XZa!7$uD2!~*fM-KyM&d3x4D)Q3C0WKZk zKB>_;c=^V)PY*C&wY0k~o@xOzM-L#LRVtot0jWWlBj+Vn@ufA6_+&tW>_g z2lDP+n(|hpBxC}n51!_2p>9LEmG+i_YL4#ww@GKF=2gKxVI9<7i9|#}uU)?IX z$jIcCjsoR3RQzo2dEr8NW{SwPqZhM9A?3a-np+AW5IQ*&xa&sl zr6`?dBPKKxxMf*)I1Lf)#K}Cy&M$8vZ%QC0H(ppweALJl}P8Z zX7{tGRC*gZu?%tv(WlUCp^!QekB2dP@)jx#(3^0Cq5(!3dFw~7qHsYZF)%AeR4wc< z+5GiJTb8b)U!~x-(e_%Ys;?}!C=enQP}MmkKb!M=suP=m;|pew%ncC+sJI(ONi$Kz z!sc8HN#c|#j5WQP0Ji{g$2CKD94Q%fcPNO+M&3;0Eh<=7c)m!bP&MDHidY08|Lx}` zsNQG(0HHS5QV>QrQo~Gs>B^;N>e=))D^0hCTMQB-_s64kYZG1sNjj{89m^+iq6*HH zC3tb>WX3K$MDIa!kaJmQbwgg|1>1FR`Cs_Jep?xov*LgC09@G{)LLDW34D0bPkQ2( z7RtU&&92SO6iX0Jm7;hIe?q;-q|cBy7Qn|nsgap&J>yusyJF(ag0GMPL%-2f7dH3e zt12}VNRNrkTjQFuIE;rF<1GrQEZKNWI#Z;viu4#0OQWPpDd4LbF!LLSB7a4TF3OUF zkA+t!EVe>T0!%STPCe?4f;n)!SEUl9XmAy@vqb_)f6g zM2P9f-Q6alIg{%P=91@7O^2{TAD-5BbrQgT_Y_h=q(-Q6MCb94?UDwz+)>@m;y%O z#3sIQc@Jrsn*YUaYN8liOQqGs8Rl$4i_X?4rQ5-r^cpGQq9EdGOu4<4mK2m;DWm6- z)TN5B3^1gNiy>DKrNx}Ji<;J{Hw0E`xyB^7oy!XbGY*Ne=6RF04p?R5N9-MId zm|W(>7LE#@ooGOvsQJy@jFFeUEaf5-x{Okw#)hH25sJZ(GK`r!u-|aJ)V)0{qMHhx zhHm2SVc%>jNk`Ds+StA%e}b^T^w@2kQ9A$d2ndw!EAUX`mi?ms%JwgqC*&)dcYDN6 zsV%Tk^A5&WI}`84j~-QZoG6^BY|gS*JoA399_Ui*{q3OoM!sEidvbVSp5m~lqxQ|a z6M6MEBs~RMEp_TAbi{CPD2z9#L;-*%vg7)1w;q%50=6)TV?Ff$SEws0x)*IAU z2)NgHlTUfM(;K6|HVYc?sQP64?wokJ^-sAua*%Y%n|O&rK`j3y{DS+{Kf51Ok!~g) zV>@8pdFiV|LD!}}dz?mkVzbwtuxRWgBO+vdW-hy6;+LNK$NyIvu6hkyDTl#Sqz#_r zVdh+vANPD2-QVepD)Eee_b|VYqf_;_EP$^d+;jZI&i61RQ~ns!ITHxpA(y{2u=)bKOEXEif-I`wUU1I8~}dU zdgfwHW=60RN+Ag@YARV~Zh+-kFhum~<@{tztSE2gS!RkP*|5Gmyc%7kRmX5?Xk(>1 zja2FobB0n<6h|C9j}>${UnG$^F7TI{+$gE-Ua*unSuTvf$4Nw(TrMrBi4>y^8-ySZATr zB2n&T!w@#sr2pb>D=KqDr^`X{!FDEZ~=ZeZ?ne z2K&c>4SVVRyC*eDs|cSL3V4H#LpYYVvSaCZho&)#yu?rj9y^53V>;Pf`T%af0k<(b z2N5#mov9n#QO<ix%PMYBhioWUYpu? zN&bPxr~Jr#(FAJ>0G{B%0RYCsBEfC)RsU}1Z7)5Iha%8TWACLOoBZRoq!-zzF8FW(jy_)rt zQnE^-+4kjZaMjjx2jD_6G&JEdS|l&W<>vJmSMWB}+An;m^OB4P@1gT2GbhE&*~3bR z8ieJfUb+0GJl~M$8db|9<`_RLWwz3XM)lrpR2muU+_2F7jh2loL^dcdzcg*M9`W7z zY|5M12p3Vdmt{&o4f;A4L`VDrV+dM|%{?vebQa3hc|=330gw@|v+`NJF#848#v*nc;j(!PJxulz;N3#TTK%bV|-tKX-;UJdw%gH_~BV zi5Kx8NRn`y7%9%eXx0BPb&r*h)8pr=NUqZc*QD&Nmj-AZ~Oj@BsU%}pH{&SMR zQa>;aJavapDvyRE!s~p}ey^w~gw=DwBab96_xz+e@6>ler&Q0n0ygMi7O4w~q^#%d ztjBA_DjrSA%mTzhZ~mx#Hk0tnoZTEfeXir|Ud)5L4bad}#1Y$BT`}I$UmsPDx@T7T zi>@Mo#EHq;ACb9Bs7!?*1zCw7{7kwtGQD<01K%$b=EyVVMVQNGLm@lyiX!%}r+okB zuQT#ypIbLPO3%A&4SqrJqaZX(8eEPHeSE?v#u$b=PFZQMBcuBi4ZKq5Fk6H?5g(kl z+38{F7(nSfcDxb(R4Gb#jueMU@n(Tk+iZ>KbJp0ZbmrCOA*$Q!!^&KZu`kb@{sF`5hU+_R!mzgtN_v{x@iS<`KT{8u?xYz$UcJgQ z74R1IDYP^9VcBoWjsJ-LhH2muEMG?lh4Hwcd(*>8UoNXXt6*UZPd(4(aA7Y%!1?X1 zcvhB3)KpzCZrLV$8D&Y_0*tew-QJWY&u3^n(LRvKeEYqlsv?lzBZ z`7!H3CuWQ4F4#X=|E`Z!5kQ|`@(AA<2ly;>U9RSs{bEERhN!~Nha?&nT^HLa24%D% zB0D3hz6}%)?iE8Etgia=wBh-%*Aa;@@3C8|3o0NSR|@<9Ck|#!cT{iVFSDfLNvJC-&0pNJW@C$bSJFH$yG+$rV+ol@F^Bs99*L4{)@r_D(pT zq=76iUDhEhH*mzHgCNAPr_(K7E!1dk^_pvFZ=vg}Y?;D*jHGUPi(5eHUr@e5)@Da* zaWM`CT0rP6{-$JJ*k>_qC(cZL1x z<<=)x-*KL*50oDK^am-N+k(Ig5nkCzM{KVWnA-!d4Q}4cuGgZXc1ookr|nPq zbQ##%^?n2AkfZ(jv+ER&6u@Z~zdWER7lQe?P&PE@`KaO|P{Si=15r_87r)##{s|B2 zx(8&JEIStoo@I^Bv~%35{S&2%s|wS`sfhTl^I>(Neuypwh1r6eZ-ua7=ZSaHt-lGf znsUUv!<5(ohoK*sCNkRtb@z$n9L}33h~V_c9U8r83-MwHHfk$O4shb{Gh!_2N4M|K zw%klI;DdCsLe%RZBzq$<#2ZfO3H9uc)r>#*fm;7)^a&3VI|?-90o1*7Amh6M@AyoJ z)2F~NaK2*n;T53|G0Mjpr4NkuwVRr+amMD^k?KB=cp7%wXqvKeCUMGR+xN8^`W&V; z1Ar=zJSm&~^{Db|9UykUvXvYMyvd4}A1=`{aQ(bG}LxTvt#4N1A< zGwZ_m%R7WUuISGUQzk^S9Hg~qkQG7*t3wL;td6_p(Rheg=1fq$a(zelKx~*bZ?s#k zS`&2xbhf_zV3qSS@vcokcjIqEoqOEPs|3yd#Sjr;&buFCIKU%{W>2)CCpfteI@Ki` z>1Uc1@*-M6YJeWJd5>IQ@G2@2e&Q^jrVIHO3<6R@n#EL0w!d8#je`BhLf5v~E~!wx zM?AM_--T=Z64}{W@}uqkk#i`KJGW!pWs&h*u`++*Y)PK6;MuPrG{~W@jnEkQS#;23 zL13guX$|JnUjRax-CS4>I-#+wfEjtxO!l2_W?0 zp!i%zY4(kJw7Elg4lw%-RH%h4lfMkCGlW9<+}qPO)!<=^Xs9gvV|ubs9iH_0IFo-J zz8cPj58zF?hq1N%E>Iq_K+QH{S2jlvk^*tIB`?8C5 z_Q~*Nu>SX#A)XYQe>(P4{Yg9xS()XX1=X9-xW?MlSAA1c*2me+pohyX*Yx4}G_MCc z!Oe7+^ComPqf2fU(`wE$bhMBcE$_o>6u02LJ^og|SUId?b>qq059t4jPB-4#X*d6T z8%s$!v>X7B1$Crvw8>NwQ49-UNJ%)Np=o0kX)7s9n09EeY(Wz!wgR-_UU#s@-c-!2R2A>{b-aR~~~$prTnd2@Me@*uiLbv$Sg`=(`MeMFB-~EsJ_jy_vIWMe&jwspe?rdXeVj7_2!{+;`>bzj+^9 zkTD3m=ovD_B1@{2v;@m0dy2Ku{FYg5Tg@okT2!SotYeXxF0HZt%Ig|PEj6?|CltC% zf_Pu}@{nFL`IK30`zsV*`#tO==O3BnpE3_yrMa9*eVw`KlX6@7y%IV+V@t}!jdms{-Tb66cW~R_D@v12}L;p7Mu2&fxtbpblIa401kCWJ` zW!wz~!#`S%=}afE2;JE_J@_HqpY|%}(@#v_8g9Yje6}eq%EId`U&L&AVP;Rn%&(3U zNArp=a69Xd#`rDZ6PszlQQsN<+3MvNqA#exs=fmm)f7XfTP;_{{+x!|_PV?FVA62m!=hI{s^$T!+{RMBY zM~&D!bZMH^!HDJ;(IuZV-O|s9VV5IK2V^tbt>x4jGp!vZe`$tl_7mmFI>mO->lL?s zd*;nyPr}+>kziayTIrGc5*9F5%C!JdorL6Fnmt2d#=6XGOA69geGk2*8Ja`2y&y}H zQ)sOs5Ph$mEDJ8bL&7{nPV>G4P)!btZIlsa<19mr((U{T+xxMoyt9y!wy z;e-i}lQ7C3JZX_^+c@HggGU{4QX7iXA2s1AyWAlgQn$)f*t8e!yKTZq$>asokY2); zWc6{AfaggCrt}QBt15so0DP~c!osCjWk104)FN6IKAgf6f*K13uY7lx1Z?2jfFN>! z;ph7SEsVLqhyhW7QCaM8CT9RHQ71P53OJKoa`enMlF+H#<&ZrPKbwfr-xc{quFx$% zjdf>03uILxML>cgA*?MaaZ;Z6m51}@RqFrmzx>bMPcg<2HUb3%6!%YZp!`pT*X=?F zq-aF=p#Br$Yv{D@8?oY<3{6mgA%aOwt{p=b*-P7>3bff5OJkZ+m!DCzXw+%`Sj`7F zPtA2TTZwq*HOpR7VZf9{k_`7sxcvY=@KipRV zx$o`(^Wyw55$e_xhsQ_Ug~s;JP9xd_NNsyb0Nudt>jCqU9a!;_6yeWIXa+t2ck7HW zXV+1TzueyH?J1{LG6SxmY-|k8tpD z(oj!1$}{D7*lavY3C*&6JZ_2u^Y$cJZHv`zx>IPZ&~)fPdO73xn8IW%b6mcEhjmr@ zW$`a?cRDO?Je`XT2F@F{hdiADo{r^ZICH}lfk`f<2@A3^EB7esYo(a9d_~O@4UL!)lOk)$+otZc;qFU7yT_HE~__iKc=oDq~63W9_sxDaxdO5n^IT$H$evC z5cHM(sIa2K3fL+-9h*BIz_jQ(kOCpZClgUs!9pf5cD8L>Yq+)n3F?^v!dIeYKR8Cc zUeRt0-7xl(ga|D6DleZMovo`Yt}of6tDX@}Q8&p*+o&-0RCSqPt({3_KUS8ha%fQ$ zhQKV`Mnu-UEmaVlf{OpzMg^pxa%yecU@`*t)*U=>q6@0le>`RkIB4}=tKw|o;>l;8 z;E!|hh)P~2UlKl6k6Q<5NSfp-3aRku9{H zleINgBDFI02E+e!nT~;b#7Kv$j&GQDiP+P4uz^d_##`4Fns0k}VyWN4=$Cx!x_g$# zw>)Cks2ZW{$I9&im_0ocqoEzvl6O$#e~FsI(^Tga&98~Gsf^%c?}noNaRc8Oe$m`} zskquSd!qCN-7epy)UVsc@_?| zj$caXk{u?a848V~>{cr-J#!>F{ER(zu?{V|sjkm_cW1W^#(%VGCn(WDtFzG0e06sL z#@y*$y_d5maa+pcy`3As>NO}(U|#|FQ>)*AlVBbTcDD}ZJG2k-77_essE^bY0m~@i zVy~%_4bNW_kkxX-m$#t&UR+x;=)Js>PSaiUvbMay<5v~n#zvrtB}w~d9rcC;n-KB?vA zwZpM^LlqM($Tdu?u!{Nhq3n-;YC>qU)>HEculNmRLmfeJ+$r8?7XGNO^kp{e zK!l0qg?;CHHmv5!2*r}Zw7QVG(U)-O6&Xx-_eGp|W-kDa;dQ(O(X*}ENaFyVx53*- zSh+b0@P5BF5EgJepNk&!3@BegRtvfk_2@iZ67}dfZ4ve8I`s+X-!J4q+BzuYAR6k) zSSGTcF)j_2dr+R=3iD6IC9M4A)(nz~Nf<+)Z_bKSNjK02^ka$aCI)s4wn-S%x@%5q zsd_2mnH2EP2L*jA1O~={pCfS#UackgWhwa#=m{h%YMDD^qZFgDaz@vM1~J;i@r@Y7>>03q!ylARbw3)3y*3?iZU3SI7Qz_ zn`4brGsf1QTHswqo8yhc@NuZ=S)qPjUh{cb^?6Y?9C*-laImlE;DG68_i;$-Y16s{ z*mVS_0`GmPZ+u|+?C6U=zMC})M4#O9636c7Y04kC-I+ZS(TdG5>xg$rG7y< z;qScm^JIB8fq-;o?5gK=i`LpK(ysRHX-p4G(^B~CLhTc{wGv;WTHaOGi3 zo`bg}?kB%5sVHs@u?7NDSD3RSWFv|+M4hTRgv~<2Ft4F8JLg8WIzqpQ!X3>zJq3oW}~L;>IUA!YeKcR<$7+?un;e5u+2|PZ6ua z`K{&1$bfW&IV=P%ol<`gP`Bm*>NeHe@e2QX;BrA<$d6Oxo0By0&W?luaE$fMD@pVZ zHol|uy2V;n#Aw}z-WNEFG|?GPZB3-Ji>2%44r(Hfr3-!y2w%3P@`kn!qFtiyoci)F z+lYQ|_jG94tmNdqinO$m@~A4(A@Zv@|NtqC&~Qs9R@nK;7*@ihsBCwSLV$Tl;97 z&zEOS;7Ub#ye7J54{x`^@UL5Oa0g|jtAL$qV~TBJuU<8Jhos33waqvWN>9Olo-fZd zupXUZm~M%FAGPX{4^`SxEpWt2O`49)xG=QtojAnu@?l6dQ%0gb@}qmA4&KYrWP-Tz z+XJNzfNVb(06p$tkeWX*uJRR&zjhzhQ@Ec3-B)^;ows6t;We5dFNHPl;ot$Lmr$UO z5-~3^%pfs3_x@lqaZcm&wKA|yO6NLta^WYl1G&uYmq3I}GGcuh>*o*V0pUaBB#V*xB0x^lagU`MkipK0iZ&b;h1iJUYrM zM2CLVn6fgh6Duy8lPjm@)`vc7t|LLA$G3UFHM_gJHCw!?c2Ism^6)@3&K@=8gNqJt z6h)M`h!`5_eZ>dVP2mu36)N&|`i7yc;8C?~q|;M~O*BXuZ3sAb-Y%j5 zpTLVQ$KJ+{!RDnSP{`zME)+lvgo^#B?j=P9Jwz}m1 z6&5W`Y`zbMEfpy90R65|MoZGmKtmlSw84rE@S;3!M>qTA6BTE-M#SPptuv?w;Pq0x4y=ScDFt<0x% ze;wN=L?79-)9=EAvvQ$4yElQ_{Lr1f7o6_^0qWPJP=Nym*~$#q79E8cw~GUu@5nx@ zSDx=70=&32i70k%)r9}Laoy2WD|z|8Q~ zeAG|IRjXB`UnkpgrZl#b`eO0W2Lom&Pz(N;MpJ1Ct-Y!5JKYw$I#feIMQRp4>q>!b zCRJ48m9W?=s}gA>TLEL?fWnd}w7ms(N9vw@Ejr~C8FD3MiYw0Ku&;GD@dT?9JvEwA zMT8h}emJ$9YSeI~W?=+QL$Z7p;GlPYhvjWPOL$TNd}w}1xp8W1`8oSzPz;>~lKNFS5hJ;L<8EfSM$QIAzIt zRk4k6jb>TIQ=$4U^Sz^u9HkLvmZJ4$xM-YsOoAe-6aKecWch+9U#*GP|5Mgg0L8Iv zZGuCP;I6?XxLa_C5L|=1yKnH|vVq_lEV#S7FYfLR!3l(aL-O8zx&Ks6O>fV4PIsT_ z+3D(I*ECEKIw?t1%hss!Kf2>*DI#s&iRDmneu`|4^0)3vu7|FpHv~45)J-7lGE&8P zR#~G~Bzib9I~e)TX1X<66qgibC~h)x>iRI<00>cc^vC&hC3k6LTi#quun0^i6Uz5R z2D*?g`WZH&!INsOfcL=qIZRC)OS>FxYnO$(qw9!0^LxPJ84@L-?XJwhZJ+!JlM+A z1MpY^I$ROm&Gw!T)D@yOD$8Tmy%kV2n|9a!XgH?^x)jyBTc_>Z=Sw%!^XW)%s@OG8 zIQ54`O}O)MdTlR_FdCMRVWYEAwla;wED%^;!5&~r}wk%6O^b5yE%I`?L>nMGif zdy1%-q|?&67X?BPnk5j5Ft}{p7Vtu*o@8{sXo+Na{)+>E#_WRvA{hy@P8l@?5|0Ly zI5Ng7)CHQ52JFT5_tSJscqUoZPw(PbbvLyvB#?QPUqm?2Z!Z z`zw+dBMm7Y)HR9jPcYSG<0} zh(22FsJC2gcb-mNgk)tf4<)(O`S19aUKOOgeWci1!N-|Ogi3ah;=uwyfvrSP8jbU9 zrqDyMY_rkJ+oI+r3cphxAvRBq6uqysa{ieS^boJv_sFzWA%8H?gt8%yR^ zy4tt@JU1}4JUuNX@5%+s%QiRJVt{W{);$O6$|;RBOW8&~K)eTx9@mU{x{r8`#r~kH z26apf*jzK&V*%SzR=tis^reqnQs08N`-U_iEEA=Q62q*LMk6n667^dBmFx@_P7FFv zOe$pv2xzim6IO&5O(HFOF3!9=)zR)DsVYcKX$8(i_#|kT_7_^l6m;(^$lk?uw0gZe zrl9Bg!L|n!%n*}*lG{oufOFa1CH9BZ?yMdh#x^Kkz z0i>Ouvfzx+jEz2#CLSXVd-$4ft-fYfT4;mXpB%y=e`;5k}n6CI|EQ7?&6&PhpuoOjGE zmr|hX*%U|LY?k4(`tc@<3ww+AWW>-_$kZ9cd49m%KsCS;ovZD;J#%am5bb@i4Iq4( zbEx8h2r-)a3#(3IplQy67L%t^xRM)@3g~{Bu&R~Sh-c@Wh2!w$HG$Fl5O;||N6;Aq zo_c(8@%tBu_nDi_{x+E5R2;Jm6^WyfI&~6e3@TogMHQ_qfJ$cd$9D>0ONK6VI>flF zKU_UQTn&h`7#GrAe1@KWcaDARPJo^lHw8vb5(>hR=t-y8tUI4AfJ|aDdb?Cz{)pIP zE#2*xRMMd!wgul_~XFW)|Sfno)V-mSD{_tCut*q5HHn>UrSq~U;TFdA4 z4aRY??ocBlC|8m!`=o7O6eT}byvey#nuE$TuI@ua#AH<086O!quUdJOy1#$Rqc?v3 zvU$++s$SArZm1r$Y~P*8ZqHr0TRYo};FKoN3o&^2@HV#q5cGjftHjW`QGx%F%HPeU zIYjwa#16CvwZ79cFCvl+Q~>YKPm^fi&>V`lPS7NWiD*J5N4o~h`U`nX3Q`1AQ&&-w z%Xq^Q_G*39+&CQ{oFA}ulNz8#Mb)xMYbn}!S990%Z{_-SZ$ELi(mNCK;KU~xTg~l);wt;T>;mKgWn|@$8)DpG zHO7d{(wD?&}neaDFqcv#%pM-w_s!@h%!HM-Y;R???$Kr7n(U{n=aIv)T%N z80hssQmuPr9wVH^67;DR`OL<21TiDU8s_*&%QLgknFMO%_#WLJMI-yJ5_CuFM8OfOFA_V|+(gtsSHQbPhto5>7 z6Y}zL)h@wPy-wE=e;es#R5#g0K|y&G>Hx8z=!ChA-Ipe0{Y7mXk1ka$J;(Tj8=c&3 z@%~-sPftE`z4MFni{aQ}b1R8jLQwII9;c@|b`>UVqvPr&kZ^(<#d)l3&d$!oU|Ym&IoOQo<8& zXp^31P{SiQ8p~4Dq&T@Z`O2g_zaklta&WE7q^kbGnVIb;KKTNzdDbz}WTM8z6PDA@ zkj5bm0dhez2a{=LUo0Wz-1yi`RXlaL)i$!mld3)VEIxZct?AcbQ|wu!^GU6s5-kzF zmwMkO=ianfVF`*lTh){Gxjp&uBy6(^Vc7nvV5-rMV&TvepW?zb|w9;~sZ@gjCpIfBjM78^~ zks6#5Tj2Ur0T{+3(%*MP)vozVp8V2oxm?%jFN?TgRp3@ptsK5I5(QX7-V|*17XasS z^g3cZ)A%v+jyB6OF^-m{^MbjZ1WpW@BA5n}npoiF>)&rHy_a7n_EqIBc3syqv@mD} z7@!8f%HCD2kN&{niGqvk-IU>bqm-k>w$uvry@Au&;?+~aN)J{xS@QNoAP*^m*jai< zTrfw{EtUI-jiTBbxv?k*aqASzhacZo_AiIgsxvv*-yx~ujARJ#z&a~gR0s&NXPs4h zGYTL`JrsaxhUaTq)x^e!#Dk5lK@*dQY<)tG7*{<-5#zeZ-ob#N`_7?~Z=PX!*xJk# zLB*!!+m&q%vFcYnEbZuoT5B56hN=p`R*9PS1I`1^)Nac15t`aC(_PffkCjcYj9stY zp?E+B0H)pbLD|G8s2qxCe2EEP+hmi*UKOzAAlI9X2-ByU+73l07>F9a^V)DMfR4CR z(!^V?w@;OQ6N4`KOd1PyQ`zut5|LQx9M4;gJwy52!&|rq=bUn!A3yuJ57S$-NByim zqKxh~Ic((C6JljdtjXW5z#Vc`<(x59rCeFAl`$d}Jx&=hUFD1jW>xh}0A^MF32P3na?iK zma4ERficVBX=;l|`Nzij>l`-)#U)1JFlh||%cQ8Hl#__`5dvg#8H6H{V~fX8IP9sP1^*HJ-| z|81lS2WR)!WRYpv&JOYMW`<2HnBP+R=C04WtM-~8w{_T= zJScQwJ?M3jh%uA2lO+8BM689SjWtNf^k%R(2hUO#({>4HQk4ewJ7O>3xa7CURD2K< z1eH(#M^UT$VnTGxmi@@*g`IPXYY7Tv(K=}h-|k>hgAl}Y*1JQz%OF3Yb55YWy&AD z(2j5XW*&xQ$U70Ny)lk}pq=AFXVhXfrBt_~Mt+8c3WH!wSK~}bLpLc<6@-R zQ8d3h5Bhf3=SA5$ZF539n)1cYBex$0=S0@Tx{tmjBmw!e%-6t=Ru#1>v}%?*vX={5 z1fer!n(-i+`{n^TK(;2W7CL9Ky^=C$;h?NMk%ddS_t69UJK~$ggSGch1T+26s;ZI* zhu>ILv^i&|(5kQ(^@vc#P@|oDENPtdTkETNlj>j~hb|Umv_DTg97JFg>5Lv6v}BCW zAmqr%J@K&Qs0m0Q>i^u`O-FjA418l(s79PNdt5J7oWyP^1rRto#wKx*sH!DiLNXg4 zt=~!+uI@G@{|MV^OQUGY*jLb^sTUJrI9iG0Ol;tm_L`9RgC_35GTT!B&8BPX@=7QK zr#=@f5AjA5C`6m5t;Dhw&fwfcz=?xQ}%ow^PIHn6J+b`qz{6sxz0H zba7F)Rn1#kL_q3!@p6YNl)p8X!;NWRIZUK^AHGC23Q;|fep#IF)B2m!lE#$_CXd%> zeq#ioxcJ;VeWjQkxVwh43-)J(TSMl}5R_z$WX>p-JzlTfUZi?+Y8#PiG4 z!(l8jS}Qo!&8<#pw||4I4pxAVP9`a%r;Fm z5~76TPScR2xQ0Iqs??+c7Gus`qEqhWHp4|i4m*+!U|SgVisi(4N%WP zjswCz$0@gE233~aWwRCpx)#R16{FSnF*NJHp`aQgd*2>R4Z7#&47Q`upMA(y!I(u*(_I z>yaqYX-F5`@RogGoYQ*c1GFApG>avxWzF2DAQZj_LHyLcyf-8#AtmSr$O*EteRMOx zay5MI8jEpCCDYWv+CToP!JZ&?wJUHX3fdzJhyl2XMMi13C7r*8SH7b$;FdP!7q{gX zWzV35V0yR7Z&LQ&HK=ly#w(z*4j~wVQ8K#d4A;~?<{=_(>}Dcbj_EW%LloLQwF#9i z?A3P2nc$%63pKo*B9p|j1pVuEMnTc27!)FR1QO$7-}GboVe*02WN7cT&l(I;ndP%d zQcnQ)`V^H`>ZLAM@}(hH5XlLKF6k0eqrUrFXUE$F(`^%LMfP_^Wa(!&-57pSr0MVy z{^x;&v6~3FTPG z@hF6A2ZYKUiUKZ^D?Qc*kUK5mw54{zuZ7;0M6A&~K~{5!6-Xqm2x6xff6*hsZswK~ zVU!7v2Ax7AW8E(Zw@*Jo{;dVt3<7;125Wx^fnI8KoZv;@n2h`z+FE%g8My)JY5CEC zEjqdp`hj8j8kqla)tv%I&Rlm6z6d#^YeKxmXkbMFeAfyGyw@uKjVsmu3HiHJuDAE6lxh(7 z0_7L25RUm%9~^ju7{IbzYT!Z}VHLn=9PT$>#fli}cgfY37wQ>L?m3_p2aYO}gbefx zA_czcK&m=$NQL>03Uj;r%~jjoHt{o3*FFRAu!o;?_!gIs7>uuIBA@uV{pAgh7^NM07Mlcea->V4hEu{np zTE7B7Q+am|d6zSb_mm!;pB>}m3u7jS@XM#6IeGb|BsxO?i zHF2$ii4qj`#n_w2&lcCgTfF&0)F#%suc~=7m4YuvXxVzDhOaJ!w`lL7Lv!p~)Yhc# z6nEJ@Z(06UMq;~x+o-@})A)Dt`9X5oM4BTk;7xn~XA3O2@B}Ryi+~GCpEHN)Z{|S=P7X>Oh{}ywX^sq05okQB zwbfy!b0=!KAm3@+SGCmS%D7|68EE0pX3r@4OuvQiavUO@%ZqpprmD-DsmS73 z*;Vyyr&0xq0Ob+Le98i^c~1$AYAw!F-n-s9L&e7|(=A7IU5*~jyHiQ=88Yp~Lo8Ey z9}JpCUSE?2Q#i9vl2xLaC`0oeJ~ zlqFj?W|HNVfjVm;@{lC(wRnX04aj2C*^8cVHCP-!_JSMYN>vpfjpt{p8>Ar?r$jo0 z`EwG!El^!n>Ey_s++ZKf!UK5U-kfYd0RoHaMQcpU@-1edGbd-ei#2O@YK$7q&!VVq zA3H5zLe4o6bgB32wKUg)vh^W;08#|)&*+=&P-(aWq0mn>pLlIG4=PDDIwa4O@YJ!I z1Kdo!)jMv^d|-syQ7Mg2cDUGTIn=;LJwgfR7@XY_CngUn9b;!s2yiV9+^PG8TWpay zu}!n?+MzA5?aI4Ml`&YuYoGnG9{knEsFVZT#07KKzT9GkNR;{0f`iCG0PY?+)Jz^E zI;dIl*XnMGBt4cR^z$*N-;WgKsPAw@hoqG%Cat8)*!40W%LzBN@4fsX-utG%K5vaz z4XZgMAM=5@Xk?3%g_zxVT=bT)*u_*a39;!Y3{K{nX+k^LjH@kw)B z6AY~zK~{q{{Oa34!_S6Cuv;!H3KcAbeTW2&xsvT4# zOTyZcw*-4pAW1f5r|U^bMH&9EXSfk0*R3e+$9D~MaNQnlQ#)yPOB)Sj>D+xmRFAy>>D)K!s^28G^)cd|QWzK~&W97KHG25l+Y;G{EZP#w& zr!(Q9>SyJ@EvrFJ>h~#-J_=pHvsuR7?d&vt$k`er#y73b<(Hu*wMuLe|%v z${E9xUo~Rzb^!yEaw4Dh`p%&(Ue2O1RFB?5XXJJZAE9~s^N|bNH2h5IPsKW7y@wOg zxU!JrM(j+JB<$l|f@;OE8NR_?r%3$)G3U;FL}8$~0&^|RzrB4R-wFfe1HGs`OSW8t zVv#XW!A9I1W^U{PehytlzKFb1*#-Hw?<+zmj&jRdE&&>eqiEGBC&nG9_l;P58P^t%NR)c6dJkCHfk9E`qP$I9V!K-T7l5wO#l0li({S$TB__mcY|p2- z4`mD07zTdwc}lHssc{IY+1}`{E4^>M)F%%SH4Lp$FB%|yO>%;GTrmxx5Z{ZkjJjy0c>3FuYe^* zB#o@)!8)H7X_e}9ELJ~+f(N+Hf~KrS%}La88oitb9OC|lo0 zy|6@0T>6tzamlHnO_9&Cp!MnT)r$6_mz~$5r;!)*hG7<;(l&IHOm;O+q+E=V%4TZaG0_~oYp$>CO(GViheL}94rCSjVhg)T4N z(ae08UeXNvwMA-5AMftIN_()v9lywCEHBo10srW(KmVI8wkoG*cPfYhbnAS|f>mOQHMXwA8C_0W%9-anY1mFRy%)|}gBKinnGTT( zL+)7$HO*3=u&tWK@-KQ2WnCKInPOiw;Ljd7S1{}JI(h3*tMMqBCj2nZm*V*#h_mNS zRu;1&j{hN5^3Xju`jEpG5OYF@*09iLC3Y(7y+qSCYw2C6pbo$3qC?tpwGsC55)%Kz z&L-9TENq&VN6sbZoqLtWu47u=85VC!^K{=xHp0Vrc2!S_pe=B~H8Mc1+qf7$q{Lje zKcc4?cQR2m?OdN?2{#(H%f=-(N`cL?_q9@F9gBP^o6$^|{V-x5!2f(rfj;FE?W$f{ zt1aJTy=>QFSFEmqmtzfHq~>^UJzgC~x0oO~rfzMk@F&E%OM;OTGHqa}jRh6+NOw3zarYul~oJq(lW?+(5HgPB3n$Wsu z!dfW|Ae;Qw9?)+^e2dV5Vhxct-_E zsP*n8_AgmsPxSmiVHBI{45yi`9|KJ>MmiUyL~4gHQFuBm`pc_7sr(S0VUj~Ov&C?j zO=@9*tK+rNQ-6u!p~rJM!^R~*#rCa{4;Z_>g&guV?lVA>$9loQ!|D55SV%wgSj-}1 z)UD0xIN^v?NImrkHL@1%5Q1TFh-*`JM7Ks!HC%aNCr)c`$9u|5N-PFQ`~Zz^*71_@ zFryBhq}SoWAs;;R^+$7As*n{f^S$zFt1!@P&9Kl{KN*u=?nzbmgi!(eiLq)R>`Df) z24WxBYt#U+M%jf{P1DG8a~8pkq*F#gn@V>rOSa*xu^!3z397NB_0X9)!ToPjMTri$ z`;8rZ9dX%TvwaSBOn+7EH#2Q;Vw#+6BiwhEmQa&58N=wj5Io7US?_rQ>H}`_-qcFH zLaQyHu<_BQz^qJ;?@JR|cu~yHP1QZHFI8)K$jJ;qTT%vAe5S87X~$xrSM7JNbFHZj zQ|v`Xua+D6RIf%$!a8>IA~<}|*hn_bjRM%UG?hQPr$TpN>QsdlE}z2ZL^XLCmnEEu z3G;UJBngUscWBTlxXSU}c${&?Ta7JB-{JS)7;puA*#Z#2J5Tc^- zy0hN{aB-b+mTf@7jgy&7ARE|aVK>%D#qWC4Sf@u}U3m%f2H_xXTM%mfP-a&%28ri8 z!5fHIaGdU0cvgK>4jX)XG%wvOnitn>TPHF@_F3{avZ4Znk79fAKbV{muh1h2z0N2~ z`WC`}O?arK=@Z%e1F1EkY|_20BV+pz`6uBj03vlLiZQ@$3+huq7-E3X7#BEdH!XY1 z0k$UBCT>B&ocrCz$TsY1Cmd31e4GB6&4YM=-7X)5Ii;*oE<#vOyb0{;09?@Cn}E|D zQ3x9jrb$-WkZuc)(2B5;coV51c5ZpKl%bq)sj0QD?8~_hO#-AEDG|177gWH&niqIM z7jTLr%HQsW`#>sWeAeaNzs58v04vlPZRy>;Rt8^Z4Tmzw89=gGMg>KV(MIXpnzp`H zqcr3nkYtKn^^u@nA=IUfDx(kC*S{@&$qaI;Yq2Cj#eS54>f%T?wL|f{GM;u}q{N6V z4=cMbz8@M5=iZk~`he)L7a8kj$b!9!0E;CfkYdB^+NS1^a{i|dA9RP4{fhndi1sR$ zg|*#V*nl5*9`H$}&6kjnv}!sGFOdYvEVz5|A~}4O_S*c&DN7`v7HX5oFq?FVxQms8 zxGkBy4tv=0`b;t5jCxGr;XxC+OaA1f)-@_&*i4~GZ5C&B6a=(24|-oo8m5p!ls0$EUs6W25KOk(^jffnMiC?!xkTVM}FYCMOHEv8OY1^;&jnd zC4gLQAy94!V@w&6LG}frH7f^404Q}(Mv3Cg`*#CiL@T+MWjpORmhaO1EUxq0B$465 z6fY!L+}!PX_XZ5>?@}M_J1}qAzvQm3`RwFK-tF%by=QuC-jC`ji4sOdxOQl^&nzC} zEl|}I@&qvg0vdPBvakmQ%J49b;t5W=Li{vKQYeOkiRI^EVh1%6QDN6%ectV#{gQef{sac*qb$sM_ zV`)iac1D9dJ6uMMJ7MEB1z>Now!FjO<u}C7WX3Oj_VXxEMnW_+Mq&khJ zZQb@r#*$YgW%et{rKB>H^pmXAv))?s9-(aXt`$qkEqRTXkh+8(8F^Yje<5P&IKa1*vv7FSHB2DC zT>O_Nm`s>7n?AX%LE8$q}L=@!jTVxJgMtj4GV&>SD6k{?RyFuj(@Afd(;P$hP zL<>U#{38hKNc1I4$un(07sVP*?1|pTho7kzZCX_M|yF%&qRxn%IZZr$6u0Fid&6;Y%EXswJTz| z`$xOKVy>0xJHG7KI;BMitfnDqTIU((Zb6u(;?u`IAC-vcdL`D1uxx8tt&N$=>TnpH z*K{(rxacBN9!B63Ck%W1m5L}`P%)tQIs<1S>{aV76s0^v+QI-J&FX;UPyMo+x$Y87 zlD4Pam*UfHL{AQ}>W=~67(aUnD*fbcpx?LwYx@?WPQRR4u{4Z#nQIDpBuB;7eW}*_ zE>MEI6kZmUU``wP)oEYw5-BD1Bp>b*W)U1lF(*)RT!G;*SEqb3#gq0Fw*kbFJh>gH|U+&4@RsAJRJ! z1lZ4CZQo3OrPAbWSchK?U*90+J$w}jc1efYpcP=R={>x;2C=_7dePx5{%-$fi|Hh* zs;Gt21dYZNzGP?i;dZ-Kq=c~}`(=573L~%Vc%uNq`|>NVX4=L9$-*OMEWbv=sSw(- z49GK;o^TCFKsJ7v_2TJq&LE-KFz))cyp+nqcoV`3M{@QQ`sW{X_l1ZNrr4tgS@RMgYVMa;`-3JI_)*^cfU4|}9XN_~9lG?W_YkX*>`|QK zD&6jkx(Ybs*jlpcF`JzhdWE|2Zb(f_0JYFAf*i4P2Ia8kI7TrP|uS;4m2}P~;nUe@9 z&Yl-l12R_g2yUSoV*!d>7bN>?Kk1iyNhXhZ;(nt45RfXjHSSX1x4K=C2JSm}Lbgc8 zShMB{vvs8hz2QuQwNTzWyrkGC6de@`!fF6n7A0Q^+uCNU%alz{W=>`Xw9JI0J-(Rn zL~paY6PzLG^Ba9AQn&uXgp#zh~(r&vUN!%l4 z3QSX0LvV$>KS4`%W@~fShFHy_>M`wi%mxw>8Eg;JS$C*`rEdcrt~fiO`$yvLQzl5C zYQU$$tWH=AeC^owxE_CIKVmJ%t*IX-P%Jz!GgPEWsI^JZO~Yc^$V|n4Br{!O1Z!O5 zZWs?72oCyQ;*t~qu-2>nJNiAg`I5@p$@L8C@-B`{_awcJIm5T5x5K&I6ctVwE#Jx~ zL;3;8D`V0ggTx-9c<1u>wKiz8bZ1b>-vQc>)zyW~Uq)fPXCT$SaJ+yy^xSU_4#RA{ z9(PR4&KM2Ze^cO~PGdC%(S?4^P1_PE+{~HsGH3*`TuJIvL>tY(mLaod=)e4V?*AG4rbYpzwsBkTrX zFo8I$-+nxHb?D<9D!XS!PP&z8{#hX0RSy9+cbQ_JvU)O%-(PXM&%&k#Zkz~ zwi@F!jjZUIqHUSmB)`s~C8l@EKuGhc#z=u`Gy<(xQYU5@weO)pi#qNNEwqZT9zed1 zaXoNE*TD6Jd}T6sN=VtaR5%Dnl89dbB2DvBYt!&AF)Roql21gEWfGQAOB6^if5N=D zb_yK-0sm!{EKpDc+JKIP4 zM}q4eNud|_+;o6Qm#?0uUA2%nLwiE)ui=)0#?tn|#>9Ytnc#a=h)T)*Isih9O^Au{ zEcM_a6m>%Vw`K8@P}K3#x3ONXk&(6 z)9jF|LgVK5Y^0B($%+6!8$m$!(4HL)`A-EeW;ezN$Cqqi<7$>U@v+SUX^|i1pK&Fp zSyBR!UYU;x1IqkeagMxm{av|G0@3J7m${Mib2A^s5Em3FG9R&T)g8&HS3~6PPP_Lt zg1{liuw8}HRn64%)53}GNen*qJju4XX&eo{nMlKV>QHhVJ)?Lcc^n5Au%L>v9$n>6 zl@w@=yowYGaJ_tosc|AGVmr&GqK3<-aBfC(S^bR+HZ0I`tDpb!XEBA(r()~~LUG;t zvQR^u?Bz&4ZLS6S@i(GgSPKH?H~xKQAia`@`he>OmqzSk7u<|MSD}Tm>=xn+adV-N zYuIY8MyEZ9g|UF2RMlL#0k%Y-OCq}yp?~S?s!$Wpe(S^}$4!soz!?xy!MZ9ouuhB) z*xZFwC6SW)M*+5zRs;Qeiz_1=@{f`$H}~|<-NXW&KO11Tg>V1h_)EwB)S;Fu|G}A7 z#z8%AkD)$wsbhlGZ|FdnS}KbFsu;KbSF!j45)Pb z8=K~UQPbe_^?%^tYqcLCu%nKZ^#3KD{0jpC39Mkj0{+fH$?*){J<@Rc1_mvHPx8OO zuF%imB4||LXfx6?_@DHI5D<)if%RdY!M_t0l0Jj~%2oIm7!Lj!%nwic|Hk)E3aWp% z@Cg{x!2itnuY5m$F*ZhfX8iby2uR%cyUTy&*Z2!8hW!i7{lDop{w?kQNs9nM^%wCh zF3_(I8>rt%{M@B~;zUD0kp2aPB7N>sC=oKS{ZE(v8}|mBujp?n>aab7>lpBX1WmvD z|5r$ezc?pwgTZt_&HCTKe>+hA2V3ozE&R+?ksk**@Q3R^cIf|p>Jf6!V8mvE=Yd(0 zg0nBZfPhegf`H)u4@>a1+Rs|~j|7l_r1H2xz@I7dmpR~H1g@&joZFl6pE>_m)ei>! zJ(EXufgjZW$?ca%YQK7YqcNT2jFOn(DOeI5Umv-GR^>qmhu{`sKAU! zCj|By-~b6b{?w;{>meKOa;$Jg3(EYjfR~@*&i5WWZA+^A}24IGD?L;KM1o@9`z`u#rfiF5S zfbAXR&y9o&b69(WC&^3jB;oz5hfJS8+S6mZ@5VlYEm z;9B=@nNUMZA>n~#V)m=ce=*!H0|NMiEZ1qlZkEH&&0NE+qP{?Y}?L$zx|*5-#%Ig-CflOcXeHL zt<`s}Uq}fr$f0hJw<_ zm&rqr%;0c2ox4fABa?u|`Zk5(0)4+tvmh$KrI_ycFQ;Z$W~Uk(y#@UIz7X@GM#&c_ z)9Mr%>rw{f&@R>_4OxOsueO=%gEz?c9pG(n+Xmx&jv;c^O~ZJGe}#zB*}#MpcVLFC zb{C2e?c9pw61(x)IQ{&UQMdt)_6MDNpbWl$6DsdpD1_(|B~uXJ$3QLToQDZ|%GC&u z8+5~h5n1!c5({OWJSnzpwH(svxA~5Din{6tef1%ET08so!wx!Wm+?`rBAHr)Jq-Lc z9Hd$ixQ*RJ2y!x~P=ntz1sQjrSyrw&?H`J9$AWc6#h&`AqsZJZE*%9Xas^6jMFhiR zl#k&3fse#5?kKL|zu%CQ{PjYcw7Y=+2|Ck;^N&ff#o;%SOK2!00n_F8jsvz+ubMn) z<%#@XA|BFrUW?8`et1h?g+jYuA#|nu@dZVgO*F&3BJc`kq~9cLJoqFi)K#u+Rnp^9 zq61XyR`74wbU19jTQny?JT&Xa)IW3jcuc;Pk6|emCke%v!NFH(L;Q(oA}K3}jB9;j zTGdKmHabL^f{1TS?b1roN4-SfGxAwR{qpN0RLc4ed*H{VY;BO4(U~LdN^V_0pXP<@p4~a{{;x{{};E!An@b=8<|Es zh(a*L|H%yAzV5~_1PI7GY~pWd9-xc^nkn9oZ3ekzhb@sHk)jPqaFb#HCB|Yv%9@dd zRvtnz1fX@CUAJQ^I#W)@myrMb!}azwyO5cLP7`VbTS8(qko&;@ZeSRjAqR_rU}&c` zGo=R~@r~f;ozYz?jXlUU`!W~%;@8v)gncpR15-8<(4wI*G zX(_}-meNBgU$_(N4>AL;L=wivi95vN!7iyX3*m@3`;iaMNQuq@Y7(4*BT5%S9Y5%R zj5DKQ1m5wx&=1Qm4Rxg`hJt(6l~m>G#7n`|h4h0gNckk0iL8W1oPNbxJ0spYUNM<5 zI-nZMEw=zu4F=G{UXdZz39KX(l9n!;$?V8swOPhdaS?iKwhA%(ouWt}M~?eW>VamK z62V3_t=Jh^7+8_44(Y1)%EA({U8rj}oAwj_X@fwZ zgKucfz2%hlF0>*t4cx%N4f36@Jc1vl*IK%q9(G;mQyW-UGBr|^J2eyH z`?I8~olE%Yy@D5_Ez^=2%PH$`G{_6>ENNLy%tFIpxWz1vrCIx@iMg10rFPTRJGdc= z@}8MsKpwlZP9=a>KpLB&FQLxy&IF&z%1gc31l?eg(kSYx9;h)JHHL*5L`grE))(p5 zMS5V`MQpezEta}lKVqsgROzRaaEGIp8sDel*F|(7C)=QZ{dP@-W8`zvK$OpX72`aF z7XgiD@AycZQehIZY-6G`Z(|ePLw~xh&?J;S`{EfhR!O)+nFX!JlLrm@QYtE? z{l!=4(|sWic@aM~C0>6`Adk3Rg6xQH^FkAol4qa9VYx{N8(nZX*dE&cWj5Wk#wjIY zvulS{02*(pCFwdQx${`*)oircNkAZIEZ!0*6W8Em)L}Zv4@4hk1(6`lGF=v;Z+V=* z=BI*=v;4a0yQWH-?c-oX5Qz1sQVj~wDN*D?XzvW)q*BdJ+1I1e^Fvsk=BFZnhU$@k zVrcFeVc1Jo<^O`8W+}tAfUQm+S#RUG%oe890}_;+b4$Em&l*zKB6+tf7Tet8uzK&9OU z0(pyHIjVnuyxE_lz{hh3jK)c@>sx$W4cE8W z!|-e!{|M|782^au;yL^h$tiO}0&DUK%z17MfMOESk4+eno>9*!{q;!8Rcbcj5%$VX zKv5mV+)EULG17(9i@HdZbikN=$n{1+Y5GG-rKfxzuKu`%(n8k@U5vpuxpmNV>Rx8m z6$3@86T;ydY{CFhS$5oFMHl^xLh6dFJ2s1Tqj?9WD1~`>a|63EciJ6X`Go4$is}JU7LeUDZeey zD4Z$CRdc4DW888#J!rY3GHXtf<;m_M=T>g?|%qgJKS-@)gjl zLX{`j&n%+N;%)jeoNLbQbcv)XP_tF02TUmTBXnC_U31`4gkFhX93J&wgup&15;6*G zvwbb)!{VT;QYxzykRwt1H~)OHavm9>L1nXa;tBzNWFUTk1~AG^*y2>&X`XaGwyzcDal6@+9t7(#A#-k)_+EiW6sm>5egbYl0eWKUYJIy--6=J z;264L^IgN!Q;5huKFTxqh+JQIHqy%IWh2X+?9-&h4)bVc7c7swLpxzo!cK0B$|bvM zMpPysvQ9JtYrx}c-~<Ih*@X*{5}3fT zJz*1SfjMbICmab8zzV4k6aFDMM6n{61)(A*bPpnAMrxn~Z>NEb_lzqo`EG@W_ zCjJBWzvHLmi*I@KADLqRhrf&dePXZ)hYfoZ0Fe7C{xtv}GpNDO6*!31H#6q`L;2*+kv|=| zZsv8}ed0R%mhC)yx%2b+hWYpF_0J%ERGEi}2%0!+)=*gl2iq&zLlPvw_@0s89gUhC zsFtOEmlC2L1@0(b10yvf?Ia%GtimPMZv&BQG8>~$Veik zJV+a)nqw^lYY8Zcn1lrrDKYJk0I*7Ql%l`JY%~h@hcGRoq6J?@In3a&U^}gkTo%Qr zGvle`Ov>hFvOmCjjoMHEWVl9b#D|W7_#~-l^0f77?qbhP#t*w0#K46*O1FJj_R=72 z8nrqWX19NBW<#<(>_%pyU7aqpZD!=`c83<2tkppm&?J?Fs>M83N3eEMqHMwb%?y(g z6rq?2#3vaoVt*b0^_0$cMc9MV!xm;*R1r&6d*q7)f84IOF2j;EcfWEZM+`WD<-+Qk zcFn2d>664~%G)MAc?L4Ufl3;jA=WY*Pwhu8rb{NO=2dycRrO;*rYKe@b54N2w$;eB zAXF^l*!1?zIHpb=q!BU?6#?|eIT)^a@9X(qb?00XXAZgpvVTIf9Zroso{Vb#m8R6e z$tx%Ao_mSQJjtlwcM6?yRBt*17sg6GQzK}&nGF_gEQ3-5V$7Qo9xcx5;ujs&-nTdN zO3__v!2L=L+RfBep?Rztxz!n28XLylka%LQf=(6(V9jleQyOliT?d_`j=~V3Jf(E- zA#@T!r)k#G^h5THfJFl9)kZ?2#QbxP_`fS&B}Yfw^7qm3a|8bPgv`1E@%k{SdzKtk zoO%XUse9({=z13K`1}+dWkzFPrG#9rkkb8^o_oQ%QDqP@8#mAJ=rJ|3)@$N^Ns!kX zY($jZv&kR;nT=11S+HZowt6Ie8(miGCnQI$A14`H*}n3uQ#EbUCA*2)3J{3PcC+Mo zp1XWx^*-@uJpz5oq@{{3m{dj z;*Y=HqZ(1$JCEfLy6>demchAA+o+FKWuPJqu&cR17DH;bsN(&LBy|6+DZKb7!Wt_U zdHV$hoc$tu9fyu$5Xvq&*%h_dN4a>d-3sJCP0WnD8RrY4NXb)R$WLCs*kDJmkEdq` z%&)v4XXZB9dImiM!?%fB_{Bp5g!IQ=ur|;)`(fJQ;*W0N-Xc$FjXn$r(J2cq5TAp< zdWdsyRYj~&c&-b&DTNZ&g)w4TtffhT*&8-ok&0(1~ zg=r|xOMh;tH^U|QN&amN3Ct^_%`I^Xx|fH=3vxb!Jk!5DkQRJ>s6~#j42zKp0rCp9 z4hZ`k+XUwE!mj`k&VhZM{g)M`E@@zGGMwguS9YhdUVN;x`@jam57hsD5@(z)AR_=! zARu;$*I0}|V_XfC@0AsRXCh~W^ICJD5k)lZKMW}qN-4D4Wzp)|cWU|dHfw7Vd+M8x z&dQ+>Cp5|zUvRLb4@gWcs-Y%Ozr$Fto`1U9E!2z8P~99imfKStEIXO4Ki{)o9H3bv zz5ARI0|Z&a%6O1=LsZ>_B1$1gl(PR?OzKgGBOQl;N^$twq!f@E$I5t0n@LyN%vIv` z7^6Zyq%pA*K=V6g_}`KH2aY~qxlwy6jy^cK@x$g~9r(j1Cf`1Iqqhz4`eFN#Nn;Z6XT_)W5-%d+n3G$}3+ALQhtxp)Lw+uY5y2G(x{{0FDcrq+L0 zeON-0eFrGdSy4PU84+Fz0!CC{ssiTnl1tZt-zbAiqtcUb9emYv=&pARyXI^gajtdP zPO0z(*Ld}p!Yl5{-KN$wG#%O5bDPC};?1&hnK6h(ZXFo*A8;L=EWaPhy;^ea%*DO8 zD3fgDTxQ*St5?zNrP>Z{96JFJuG34dWrQtxT<>Y*;ZaAHE0~!!lSWuAy%7_?bs!di z0AsO!kMrtfZ%oIPa-Sv>Oa|*2RI=l_RDzm=;_JQc$&LdH2;VWv)0oPwoiCKoxeM{NFvdoZ4 zbrdenS`y-ZMeM{T2ak^>j6ku0GUtZ&YrmGzJ0_GAT(`=`w>< zZvDCA_3+D&9KQJP=6)>A{0_!FV~>fAdAq&E-LFEJ&Y1l~;Q#YjWa82^p3}wc4(I28 zMRCTVwu#HLdYvhgE8*C6Z%oUsWf}gh(0N*SgR|eU`0qZvpTD{?{ej|n=oqDTqO=o9T)PsYvz>f*`d#;(X3qY;;*$MWhWUtpf)RG1|Vd|HvpP&9pA2 zGvm-eN0)e0L&p|-vj9YAg4vO?6MKySm;VHPY68e>mYAQ(pB*g_94U=~5Ys0EnH^AW zr{r#@UdjqjiYC9HP8O>7RwN*}LK z0C)s4crd;Gh0f9GF)Yj!0^OUFQBf zoxBtjAE>r3w{w(-G1I|eM|=R%Oob#zA77x$FJ0$ww5s`WsGOq%TDOy! zswZbWK{O`TMDA=_H_S=8*BRy>=yj%WsvHX=vl1o|THPe`yC_V#9gKH=pEZ#kLVXC( z1?we@f@Tv`q*TXq;Sb#)mW%X}XJ+N)F48WG9HVG)3(ur>f|pNzu9DBlGie*Vd%qy`^>nTdg_p?DE7V@+ci7nw~>0~=i|vW_TZkN&?B>+x7!og}j}0iu_jjt&ks6qXiVyG?TE+{JcV zE%LY&e15iGJC-6p_GSu}Oe3eGFTM}>H=n&PJ@&o#gQ@yt z1$3Wxgkon3MQ^|o)}4f71)2HJ^r&xMN!P~`;hkq>;K36i#3_Tx6+`ixeB5@y4dj{k zqlS=I+_zRnuhOY~psEm%Li}p%S(?Nq!F3vQiAstYu9=ZS;sx{ICGzm_cH-NGtlUi+ zI%`Bgx2bH=q4#D4Q*!4aTE`ZXp96?CwNZhQ{x*NG;aWt0@uKXxATcNb6T(O!Xp8=8 z+tXy&-+W@`Cv0`~l&p#hl1U|F@g75A13_SJys=cUy?I5(%9W21lpI>il?^RvBi{X_ ztX8Id3O^s0B{oY<#CDmXP_x=KOw}kiWId+2I;OCp9r>@&+k|d!xb~~Te}YqL*hzyj zMYdWq<2*+eH_Nn@1pi5CRWEy41p|MmG--Hv>CvdOAhS|h-*%>y1O{RqUFFsf^C#eV z65V^$x%#Fg`S3AFzp_q!vkM=In~xeA(SiKd5GCUOa|3!F?sBh-(rOIe;2$R zK{m}}VexN@JE>BdOd`CI!e&ZEo$WiCtm4ZwCUI0 z8jb`HvhY^}F%yLORFO^HTyt9=r-%uuy`ClyZMTx{eKN5N0?hq7@5EQQst$1W7~@EX zT;4j>SwY10sECV&BW~oYOmNY!BYTOvgtxKk z?&ijffKiKjrsF$!R%p_ql0mm%GhM$TS6R@`)7xfdVcX4LeQ_KQUu`Q~c_nUv zKaIalANsOc_jNCnLDYN@as-SUG&Y;aSklcaKl8n6kSos3%T+ytd+d_#6`iekRC-U7 zhRjw!8*~vOOS}Q;#O?SqmT#B5pQL!kA3?QQ=)~|swaM`1mwa>hi~644PTjh;T8!_n zknlMlb#E#Zkz0>CEK)hI>@4BiQJfoNa$R&=j-0aW%l6qE;)zDZ?J+Rq@YrE#DRD2z zGSrcwj*SFm1=V@Y>@2Z`ifM!3IybvNlmKDF%ma3urfSwI(anawi>%YW249SR6lk)~ zTB1gmwRmR@8-JV)4#$EAbVn`o2^)=vaqhcnp|zEttGqY#5Kq{tD2f<1{2)2mxX$-3Z-xsv}S-&Lfx=O^vHKqIqsNT8|MU`JIv_R|#%b&``?pY0pqC>EX)no6I+XZg-U0-qNG8+$Yx%yaBvo;) zO^*=6&m*fXq`qz~WYU!Zqcqc`9}NgYCpM(FkqthXQhlY>!wm>Ve_ktnbimO#`1Lp6 zhQU@1KCD2= z{lRC@#P6e6qLtcgdIyN{Daow-W4rDrs^;&NyFN!U!9b)n129$FyewyO|LlRwsSZLz)QmRPMd z;%OW`g$f3}pPK{&rzDmswVU2|aMDq*8Xk%P>Bj*JyHUTuk!Z(5nvRe}iYQ+7PCpgL_lRqiN<57lc6O^PL@!Bu1QudP{yh8l_)vV(Y! zQP_eL9(|kr>am#c-d20ftDH`hYO=O;?}Z8b-zVT{-h;=6w+OP%GkB8bR8QT+EvO0! z2*gq5c6{@*iO!yQL=l9ia!jl#@P0xYnR5L&&l3Y1N{o7x1gM}y09z5u&%^8*-G=Qz z&?to|C${74_YxkqzMWnZ(S;oDlCAT3wwFS zuL%6Q``FB75#|EYd+WO%;uh{+dC={uViuBNR;E#{R8u>_hM?X`l5ww)xIJVy!`DX! z*q6(>u!xn%gms}4XP1Oo;^>pPL{J~e$2u14jAs+PI6u=Ak04-s1Wl*u7~*Q%pCFWLIf0^dO_h#+Dd9sd%5g45notV0DbJ?3i^fXO z<5^Hq*095o$*^+s8&89u%qa!^9yhd)Er%5!9*T?<`8U}WXga$rG+f^p|6ttHDT@_x z9m08PW0pDPROpqS&oacDqpgcmmy(Yy$y%2!$D8NM2++*BWfXIb?G_?(OWgblb^>Pk z{N)E}uO#|{Ix{?Nh6Mf|7?U``A7thW$o9Z3mU|XcW|U#w+-wls-fKxM3pqnwrdv3d zG_8ICR784cjp`N6fmM4VRC4r3=E^hF>ULauhOZR+5J9-_%OeKwZGhF%aNYv)t=oLN1 zL##7`Lj^I+v%7F0wRU$bUi>?&yj%9jEpX6|wzp0dILs}LPL;Gpc+)S?mOdTx{H@>f zezlTydx0MyvA1rF{7t}l#Ob;K(*ZcxupCg@1$kywhVaER`Y?ROct?2$p8>AUn{`ME zz&_apEaVPJ>TG1IHSttifGml2CscV|Pobr~F+pJo#Qb<1bs!$t2gpzz?|_Y5FvP!1 z5UWXOQR8A*EFvQ@EF6dZ0IZ@ss53{1w$>nkRAZXH>LP84fc`pD8R4 zoR{h)v+I^qCaNEr5I$N8Gl8Y}4^0ouSNcV|@nY-v@mH<%imPB(XZH*XdU$QsGAI{U zkuDJ1dFye?o}F;PiUu}0*PKpo=k6jCysk+4CCl`ydhnB*hCdD)xJ_KKFW<#o_3EA! z?=*^CAvqG|i8pf^CmAtT zLelR+6XER~B)2G04iDCHoBDo`fm^Cs)Q?)~;f=Nk-Qe2FF(4m*>o(4Or(;DUw;(eU z-=?`{1J;2q&b+jBbQa&u25t;}O0A3Vf0W1^8V=N#k?FFl!M8VA!vGH&)L-Wn4;UvD9$gsxLd8{M3$23r+n0vExNPuo@lPp9r zsqWO#!B0A5BOjma+eIcpitf}D@CmmPOH_a31k*h}vom66Ij9r#+dboGpb(1>gQ3xOitOiTxmw^PJW zsld;|EyZ!*&SwCYzPbX)LfSowgP){XM*yyD5=!%S&aW+UVH_ zS91@PK@RPe?oH+2Opej(RZ)w~bt_!V1&rQA{NmOZp`#I|VA$Wm&KAh4E!EYu$W5RT zgX!+}1!s91`GNd0WFl(kyt0N4|0m$|{KFJSM;=}UF7@UuIdVA!_5CIAzm@Y=pKSQh ze_gm_n8Z9p?8Imme4zG*Ix^ot14IRCpA8eZ0~mVdu)K;iZXPR4a2-~HK?-+KWF3RJ zXi6C^+Gz^C8t;Qsf<9->*Jw2Vrd1k8H`0H)fo&mJW z|F`L@qlP$x8EF@Tp}IKDvB~COSJZ!I&`>G_iV1sCb8qm=3&^{h5c;H5n=a3DB-GWx7fVbemQm_7ur>_<89UTScWfH%=?vw4_1@>U7kMC3mLFZJBxo zanlkb24N@B$uzuSyN;ixU|^-LDxGjHD?%pzI!i~!^oT6muq2j)0@hSaP3?%Z>Ze19 zA`da>G+8}93exn`okqACqTwvR!FNN&CsaLDG#@ zw#6~&-Qu~UQ)E)y?dfMtLo%A1(dd$S2lJLG6wjPWmDXN*^G1p z*NJML`X2FSU;tqal?PNSxQk94H>h;Ect7F-svE#!E%8>=KPCz+5J@Nv^%>v;D{ zbQ+!Yi$>_MdHP9>+C_y@$7>6de3GVFu`WvO&&XIqHSy}OR%=N*zQDvft;%;@1Oc@I zXKlhiB-Kc4)ftBp77)8{CMM~>a2l8WMF98ZPfAJ^dP2yf0D!(6At+h+88KP>?&A+ z{`kQ{5XbJ5KKaE+P$`7m9d8DO4`{9+DsX^B_9JB)6HyDXbQA_H&mp_%W`MEA2nfIR zI6Wuw8>Aa?$`ex>lw3cU=}>To^aCZ}a!@2{A>sp1B&aQt^<@vzH`!xc1I=zP?kalXqXVr8hV_X_LE(}gy+E9*Hb($?r@_7vX|&EBcP zQ|Auq{`3>3=kLqvNx|)8FEG1+#%Qlsi_|CJ+W7I)QQhAS77iAzXp0i1OX$LJg0GZY z&M*+%Jz&hkG5!2M@yWd^d$>bZ9m@Z==73Kt9B=!NbV`=!ZNQG({^lFf|9_6E78-GR}O7C`_r+8 zX_C#E&3r1UZJ|kLD}^}+fq!Oa5suI%D6=uQ(EUQcDX1N2uGmi1c?w9!Rq;YA7eTLVo`Tn)SU12}^2Mkwt@@s*bL zW+*Xs4Y2GZj8G6);_s)KyTn3TCvMsIdSpU6gAcDzx$G-SeWs2|Bl}NiN9}W zHP>3;{+rob;J2+}P_boiU~SkgW}TA% z6N%}~N+S^(65-Zkkt)hnV>fZ~q$7^&4t-KOy(w=j+@ImMm~om_*CeZcvuSKe+Q8k~ z?hI@dQE6h`V01RITC>z*wMflr@rI8qw7gPr5VI}@^iwMomAcpF&?{r91$9ir<7+S3 zA3pZ)FSXf4!*+sC%Ub`d&M+NY-#}mEwMM5cbj|78qs}_EXLJhbR_Z77Z&4@l#Y1YL zR-~*Nci-&fs6OFy5q>V}Kdt8Hl1M$I;{(?I@rqS4fQ`Wv8^f4XbdbKl(GZevK(B%(Lfu{_*qZ`>-XpSOT*fcUtj{#f|WJ@n*RGYV0!t3CJW-i!hw@S-% zN!sLWlmv9h{?YVZSz;dS5w+!_Vuff8i#IMlTUV7r^L%%jQgZk9#o94jqHx)Wg#z~P z%8Ox8TUh&YQ{k@QkA-ikRcqjDzX&C~7a^H6`kU*PST6F@3FL)KWROvY2NF$Z5E3JR za$5xLMX1jC5C_BJjQ%?OT`K-d&QQF~BsEjz(GzsCF&m8QeR=gE!q>H_S4%{wwjv? zt5tcrWxAm4mb6h3!P+CTEEG;_zLQK;xbOYSA6a>ME6HSztKwCT18DMQAD$dI;IWkL zI;s+u;Wc(jXSr(YNqyVy7iE~l!AVWQ7(r6~L>52|7BDN%Hkqhgi>XwNG6F_MRIion z|HD!-EG!%7qX!7|E0oh7&@|9j=T*L96KLEyZId z+VN3Jc^&R-S7<|w7Lt8$U001fZd-=!DnjKe-Er{pLy4P~s@;ykJOsBkPct(Nu@qwv ztU9ZTRbb4XraXJdG}kYC=M>X9(xsO3QfY52p#08Wg7fl;jh`MFeFC->E2MXp*BNBA z$GkeIo39-V2bgC(vg_Q;H&1Uf2Xlqoyq~g{k zC|}+e>hfKFBfPoI@&dmr&1&}QuV$^29eY%UA;%4Njple}YM%d@VYKNfH+K7q`3)bXX8Q&NfkpykbS z#6!@QGcpQ7_eFM6wWf*=L+mF7U3-=gFKMmn#&eA-{ak+KTZtLT3$;||?M-@y>cba) zj%xSZ4CrU|K4h(sJ-u1*Fr0*fXH`^&+K!*8aD!>&^;f)R*?0o~pw^iTVLMI&^thWU z4R}Cx@Ly{xoq)g1Z%YrrzWZtho!|}PDv4^W;tsvmTJ=Vrl6#)cbYfpurgchvF4V%t zO($>CHd|+=Xjaob(VX=pvuFFA$M1M+AenM?`Xc^N8aI|<%M2Y)6S79G*JDrFX9~U6 zk+?&Rz8!*U-TK7H=+IllAikl}Re?pNn&3k7sE!Y@5(C{0{?VR~7|qWGYBI7j#CIT_ z!(;H%dZGEBpEJc8;|4b^P9x^77oBAIv|AC)5^7X8q~JAgw(~FrdPV4o&X?l#?~8t* z458Xe%*9C(2pQu>ci?|)tlx;J?CTNC8jQ21aOc*WGRqmi!K=$2sJhqw+tNO}%s#%( zzP=vL?gp}KcB@@)r_XGki&*GkI`Il@Jax(O>JwxLO%A@y?Kd7K56(Ze!r(VF3z06MAG~w|5e|EkQ0e=p*TOpM#{18}h#go`gh%Bk zh%wUoiUQZR5Zxjk$ikHmd|)CTUZ***C1CVIe1J|Z8Es9^XjN>nM(v4&F9n7;FxQMa z`Ib$;?Px}6k1a0ppY_VxqR!S%A~&pNBeY8Hmd6$%sA`0>oOwB9I%-{>L4-IkWaJAE zC^2RW635fC`ngy*Ul^;oYq`}R%bmi~Irqy{by0GpdkcJ|=Fz4s_jvOjXZL-M$05Sk zf`Lnn(dI++`*C5_PwJRFm9Buwr){fWp_V$rD7aMn3;xe*EY@436sqO*inQq&BPF&v zY%L0f7k)0Q1{*}QSPq+K|HyNi!&RDUAu%F~T{ttJz#*$bK+jE=5gAXhwe0FAB8t|g z-@Q>(^gre1dO@A^1{ZO1g0gqvE}?kgt$+@Eu=2H(g(%6bZJ{H6MnrqsT#?&MU|pVh z(a(A#y^}~p+?37&8a{iA5Ysu-Wk-1Fl`OrWrXrwIQJoEwf<%g|IwYZyH!{q8}Ol- z|9Bns0WQ2EQYuAUh9$hYbG=p*Q@VxDd|luor{dC^seUtd*h8uuq!{mbf_~)TD<%bt zO7DD#EF^W0`(E7g`3am%6-;n{HWx1E*G2XM_&_*&uw3za@P!WmgPsch0Fq*fO&;h7 z0W*SXU}?dG;V^1&Pid7Hy1~`*8z|2RlK;BZQJ@;3Gv8XlWKDs~} zR^bVKVpZ-lfi=7y27!BJNBQ1ttrL)uU9zTs!8TkUr$V<~t$4I=%a55UgOkY@4O@-+ zuE7uSzmYR$AnBI)KjaL?00E)???0EllR1OAlc9-?DMMl|ICLWAAm;ycr#;X{0pC!Q zdY;{oB)B$0bpXcQU$m0Ks5ZhzHc&P}wl;MaaVa&$!|HKOF-`lDi6t6drS6Nz78&f8 zi=CvlrW|&{vqf?@A3ZmZJvU81m(mTibfc?CY0FbxZ#!Sx@Ua4Kb0i=w5l_@^yBGlN zTOY12K|}`CTVlYG6R;hRLH!n$LGOIH+Uc1Yz%WXK-lZ(oyjeR^d3ax`)&*Is=8y@S z+xW>1==SIq?bJndS54TJ2e|Jcz+S80iZfitU)U?xL_VCnwX>)!}?GqtI`Kx%@4 zt^gz|56$6X57A*upS8%?F_zy?`^K>R^gushJAP`8--&{;UUyP3k5mi<8n>3^lXOn6 zQJ5U#H|E{1SrM}baIih6!>NaR43)32J=CGK<*(d5`TLo#DS}_u@z>jmJ9~kJz9gY9 z@K+Z+ysSQXK%j+UA7+QVKnawwmk~9m6=TWy7UP-94?VAdgbJVyZ1T zO^kFZq=TZEP-6KcE~U*>NTC7=tfEU|!Hb0Sp7PL9 z5G`sLWJmC)9yHs>@;~&q9flHDD4`|c&oWm7nM>pmhab+WFK`KllgU&l+Ql2URu-#c zjW5&(u1Aanz+EUSKwS_9ZyApiS2mcP@fZdzrbv}%Zf!F4q)D$g-iWS^jsK~XAReB8 zVQr>mf7nldOgpc06t$!!wx@GjxUXUj=C#t29s{pm;f#bCgeAZ``^}eL_lV}{FLe;j zL~wNDLu(>c9Nl0u5@j+`QZ{)^L?^Q>nsQ;baVeEFK6Ug5c>a4wW3in4 zbF5^`j2|r~9#w~uU4L)F?vtN{*_^fULv~(S*n%vNRZBZwh$vNK2!DX%g0~nKQcMIE zfx@ZbWBtnBBO(?VEtL77AMNbooRO4SdZvu9GyHMcU=7gnt1)ZH<;;r7oZ&K2|j1HGfWMi#VpIxg?gf+I2k!|H3-kSSI(+_oO z>M3JTEKn4tTvL8vekS)TohnvbWL;_k%M?+c?P0%b_I|&Xh5)4)zO^Hse{YU?xzg!o z*&_aQZV ze@P^m@gkd5rZ1_88yvhZjl_s4aj|-4qqMNr>%5YVYRN50xqatlyJaTIZv!$Us*q{U zmW5ah62Tit{dJ^Of?SA&>0RxD)zF>_gwXuzrFWH$OyEQa4K+dptwx|FVnw4R2{Sl< z`GpdzTknHKHKuedfqB)8%06rYjo^-K5{_1hl64Ei#N}ASK^hfw%EFp6G(&L#^Ry}l zX|&7rX`Y`3`xXJ9{7E#s)3lB!^IOzYRh+5q&%2F+@JCkbeRD4oG6YCd#mMIPUmB(!vx@;x-8FDn0IX0Zjh6&YH!&&99gbe^dI$oD-Qk=!bl z{#w_zA}q?ii;C*KVQf53xkpCR6<<5_-qSJkusBqg4oA!UXMNqkUoI&$akUigec)1)2xv zf8Yq#im9utbe2;?E}s38C2fvxZ*LP68~^Xm z&GL|ume4C6Zm<$OZ@i8*5@47~dGwd4wR^JORSKf8M>nx=xMhn#BoWarICOj?;^_V&N) z{cI6>^Ucl1fupKyY&T8K#h6ZUY5MMJyOS9QlxI$jst}A--|=dz{H&Zq?YpV9$lzMn z{z8fYCKZg&mPRPxm5~IxdsV`O>`9&RQAcX!ztcqgnu1(7o}C@`tlwn0?5vv4pf_%J zo870NSj^LfXV^MnbZejnFrQfcl;DVKjnXqn7*j_->dl-6lsUV}tg zhpIYETt@_nX`((Nok@GLoQTr+h)}U01M1c5%)tc354;KBv*RuNOrehv1|XZDm*SV{ z<_#4PYdf4-|L8Pmv^2&{G1;G;Kh{c_?uzy$Ri?w#PeB`!EgRshxn2GWs8OF9lt@^I zKWEn5JDHh~uifeNM10C55FNz(%ufEEXz{GdNE`N{iR$KdPr=6UPh^}mc@S!oJE|xqyK|5&sPqMrqQ!Bf-G-AKF zl~Aw3#`-Vv8Ly~&O3gL~XCV8x^^5Jj?M$9AM!!)TAB4k`CnvvWql5dRf%3J#md&_< zH6$j`)WH!l^|Jf-a=xby_dI{(qwYx-vgppbGL5^t`L?P1U0#Wg0}8Z7x2=~Hs> zyJqXn*%*k-buBEaomfS`;Agwx70ZorxtdfKGs{}5sJA@G?T)?BG;)XMlK1|I!SQa$ zKcnQ{{^<_Ank&ZN@JYso-;Uv?(B#6NFwk80LHxkvy%8-%d#=Q~ghQFLpn$7H%&}Gx zxB>oe&b;>a=)i#l0hxzSi~+~_Z}1TbsD7q_rh)#$$4tZ&6+x+0MOobxLOIl29Y6`D zWhDv$(cI!HnPcdZ7M=(bw&mWrw%)zw+w3MrgYuM>lXKAOcW3+)@eOBtGfgTeTr`?6 zsD0gi^Wx|3c{BYRsQ-cRe;Rx1uquN7eVFb>xQ&VL_I5v7t$phD}R0 z9jetGV%bX}P^rt4(~;88X03>Pq?WK324!M}Jq46cwm}9AkBwNhvh3`~HLh{f;3iHY zo}C?Lh~>(+rYRJ8HMm4yd_EC^Nq|#1FUa7S#j5V%@w!h>692bKmpSfYThw+?XdY^UG9OOHl31e z&V1d9+F-c!oGTa68XR}_i@N0Z-O`7q9`TM3r3vUP1umE@g^oXFi!}V*a8Q^1Dz!j- zEwTv8Jc{;&$*{O`RmXW=3nW3gp!F#JT`DiN781}WX(kG;sf z7yCfVfS5+0LKF+iTID$SPI%L1A%1ogLhtaZ^a9sO(ADC>2)o;)^9heq$Yyv*Ay;gt zW(+GqJ<@5^Ec|BiXP$0_%LA9dD}7;iz|n%4*Xij8-HW|U!={+7m2&DgJ5oN1!?N+& zjo}uvYiJ{Mf@OQ4u%C}iyXHm8*#f~W8+8^J1D{!<|a-@q5x z_TOB?8N0a=?n94={5+~iE8!V0K&3ucYUi!_ghE+ki9((Ez~&;XI*1=Lj^n}-4XA^; zeXlM3@$LSta3&U6IFzGxhZxZ{^CN?54+5(k~*CNVi1&1gS684`9zp)6ul z3#N0}9+AFKL`?&GmoZh!WwSkCkfq2q_UU)SvJIzoH9gS;clck7?NU?m@=^VGLVOy1 z;KaSw6E>MGFr+B#SCTTq)XcGmKLhZD)5p!|gACCRbu-?$JIMA{<^WY@pps+gcj)7g zLO)PN#34R~Ps***-EsNp?@(_t`ya!puG%lpxc^8eQf54JPK;@&!x>sB%(tUUZeX;` z_Y1gN6Uki2F&>Y#UzXX>p3u$>HH{udJbFi6i;AFxXq7`5q_~N$9338c!wJZ}K*>FW zvM4jawo!z#%_0C8d`0+Btl@=QiLfO4MhhfZ^VQP|?^T5eN*0wegzhz@XHxWGenNF6v08h>ZEK);T!5j71YkbWO?EUG+w9`2G0N-$)j(cUl=VHlcNW z33`bnO&RWY6p$tVz*n!q^U&N-f#IsO05uD3BJgj55|T+1V7{BR65Jzi ziXBti7)i*d5QbudV3c1-n$$bNA3#;*5}257=O#DjT1+`gB|GLy%{5pz%@;E1h(rs1 zUq}DW@*UZF?G1v|2La~IMu)T4w~SY9dufe7AI4VDAwoCE_#P-I@q4m};~de32rv1V z*Rw)=0BAU3B)C>2)GaUJB~I;~Txg3MayF~NxpgKmL8>I_-FvwY$b@YLTQUtafhnMdUocTM5(7pNrV>&b-T+nd^-){2!bivnyvy2y2Z=JZhSzU2ge6duug zW^4tzh=7vmJ##kukTN!OWV299Zts~n}f(zDf@ zkLmWyb{z7KV+Tny@l2yueS3oLPZzObxeS9#WaHjIxgLI=JyurBb`9M&9)g|1c1Q+j z((g-O^AzKx!pvBT#uM%O#{y#)n<@*6_zsKQq!v!aam20a-@9}TFi&YNGQZKJ$Tao0 zRqF`4HX;wUhnBFL?!f-U3+aQad($#0DW&E=tPN5?1y4Xzw|j!bsr~trjE}%t48(%c z;$N@&G}%#p{t+7d&5xSWD}~d}agGKktLEg{AiiyU(I~DZDPi=Dv&h(MZD<3X{)hRx z?$bbrW(pO|r9i+JD3zy$4t=}f4t;wOkR0^chAo_42a{S)P{<7D0NeRU*WDD z#6*(c+!(MDFh3j;KX_c@q?hG!5|Ed(o*8hvx>(AW9JByM(*7j?Yp?fFIjqzsz>k~F z24kw|rFxB}8VO8RF@Y#&^YOO8ryO>3`W=9lhXib!39WX8Ze=aym%wdX@DD{HG~EId zFP9q&PvDC*zieWz>O8pS;tOLmyS{b{z3OhMhB<#1DXR zM>GS_-9`89UI%@QcjpoU3d)K&vU+E7n{Pg^v&d@j4ePv~=s7Ek;^Tf9ULW1b3Txlg zT?4@S@VcAshb$b*d7q~)D&TWSb8Z;}Zjpyl!{2v*F!Etg2oZ#4IU8$1O+Z-kL4v&{z}ytonn2uIU#!oOUkC7xk>F)OsuUM# zs1s-g$+_1T$MKa2N#NpKZ4U11tUeL@QlCKf2ni_LtxB6OX4@uLL93;o4b<5#d$spH z(3_0}aE>p;HiHC-=d_%~>F3?Qx8LTRZXn%&L~b)cA~@G*Ya&1GU?X%S^e4@W)N=W3 zJY0ZhSY2Et=K^YXvD$z_3X{yhEn0P>WqbV6l6=xxWwImMcZPkE`|l?oIFHOyT!zOF zfjt~D17hHy0d8TEZNiNDRN>{=AfDiLB$G468)g$8+Geo@!nE5&S~h8hvwdZqK!P1H zS&o214u=3tPD`{c?2-e7Rv~}QoJ<)w%jMF!2JP}scpbW)3?=}B>kD~Sh4goAWAqLq z=L`LeLM=0sPdr>n{Fu=7sC&MF8p)X`5$-s&i;-c2VLn$t)FYAc^MC$ESrZn(Q3O*0^5C5c0%+Gm&8|E; zqChtyr79^UH#9uNI50f6rF1hgrhGocqRh(vo|UtLovogoTg}cH^*t*)E9VA15JQiM zYzON9%!FG8$M8ZOvF{iIt0jSifWU_UcJ>eg1R_RkddN}y&RV2v5wL}w(RJL4RZenM z%VQ~MhUnkIygc0Ed7OIKwdLoi((UF?n=>y=h{0xahfb$Zvc|`wf~AEh?2-DW`;+p< zfVmHUaml>qbDhxZ%+^E2sJaiSs!&fE5rh$15I*%t3)FLQtkDD((1JL`W}A(&jMEW* zdNQrsru?I^^Ef5zDECnzSFO`yyuU9lzbXve)nkD9m8izi4}^M70W%Et ze`kdn{`j3$9d5G!C*@`y4f(qi!;%yM^Oq>`N8HL0a5S7H@Mkm+(7@>>P;nTp+TzpR z9}yKNK4`RGKuWg(t!QwBv_w>(trrfEH3Yf3#iaoDw>oprd&p-L6=#4VJ@^f(0RxO; z2K;$}S_(2i5$~-MGc4i#;K!KFE2jmIsRa&Q!?yl1r+40Z5;6>=m`Rb1$z}m8lZwxg ziMElyjb*k;zAijz@@(?>nEoTc^C2No8g<%4(Q-s;_{F99r1h@CLUt@9;*DSiWhl4HtsXE$I{<=9!ichVzCtR;=;J7^Z^c~pF9^e zm}vJCYNc_yOJC>tRF~TJwOB6{#9XHNcEy~;TvGSZ#*I7~E({7;@Tyk3OzLT}(SzuU z4by;_$XnOTwTB6+)thb-GqdZ}c`~t&+6qum(h|Ekb8IvzO4k0T`{tj43o)uVX>fJK z)?4)4rmBpebvoN*fn5Z1j(|}ADouRBa`I1;%t@4*S&4t*K#R4-leN?L(5oRBfJC*JCYT*GPsbb>xC? z8#OK$O%ryAC4|ctPCj?zBcdxoicULUeQ(%hAL`Uu8V-L!ilYyhx^VIk0r5C_AgepI zgxNAkt}&V5el+vjze8&)-gJMmvC7p2-8wx%2&veOleuRfbXqd6Jal53G4<}@#x0cd zjT%b#MVuRX%GV{rr2K5G2~S_oCx#K>8(l{vA?pZqdO{(h_bl7;T(j>r{NojB&zq5VD_wLxeQ<^Xj^!n2i>Ejl@e`^OlS-%&94? z{>R*k;++hWv0WpK*HIB>i>dYLFs2yIR&N;KzVKt&yLQ}TjmPy484 z@rMJZehovJ0phhgG%Es>yDuD#cRUP9pl_(p2j^OOo<#@N;GVCb;7^GTgG3}T_GAE4 zntN#8P``#Ha{5zY{EamArb?k+firufpBS8F=v%EZYw)z+uQM*UcaG&~7nTr4)HjIQ{Ut<#Sx&F8L{?jGd{YDDpjg%g}rHaSEqbEx`)7bFH7Om`lOjAN_JxqId;26 zD!Zwt!Rv)L9)V|%!9o!xQ6L}%g9~Vn$nwHBW%Fa>2MhurXo5Xb;xK>UxKzHvrwYv9 zUW)7p#pXqnJYY+ql*p$$(rE?u5tYa(Z91+4Q|!Z}QXLI#uN5Rm1FL&-R(skO?t?J6 zD1Jabu0_>B#pVlQDmpopj#sZIYt>Nl+8TGoo+J6n@Clz79ojNsD^Q%5I0t$fZr)!7_ zQmS5fwh`s13*PF%b&Olss-$&gR(#AvZhQ|yj_6Karm+nv%8C6wy0pM#4IHg%RJHWX z9>k++w}4k!^a2Z1Ja%^Siqh8y7!szb;&Sx-g8;`~6GN%7j3`m`uR0lTU>zFPfe*Oq zQHQLlD|6G$U%subZ~#rlNSi{;w-9aq?PB|4 zE&x7Ch?=eRY_xB4FELPHj$R^2c}@NgYx#d@~OaCsJU3I<3K!Qsq?Zk^J-VqWEKYgESQXIO%4gsEGN z3*`B=uA$KVh?EpAf&kv8MB8{6-OZ+#dt#!kTG!)2Q!Hlwl+HD zU_n3>(*NmlMBzZz3}VUb$}1cgLHz;z-7#c$IP zBnE+gy(IT>i)H;>V)-9emRemWSyVbK^b3gfbbVTzVu(z5x zEXz!D(-+}BnsSwT=5h4XV{dyTBK?iLsKea|`ANpl__6ezD?GsclWKN5?7`V}rMw_t z<`f!DYElB%=#*6aY&OvQ-A$E3-gm-}gz%TI`XadVPpDq}_r@4eTF9LsK0 z6wEj07tbeZpzSbnO6u6+BrTsZMuTab4;aAFxdkn62|YVfQZ+XEQYp~nHlJ}mFZAS5!dMNhjCxeG`+s(j~|Ov@a{D` ztnu5k&?%i_PbIT_Oi)m!X{#M!8#ivBa!}5tdIK zup4+_;z`=JHe~6z?GV}`oP)B|f%2su!PneyEl>e2X1+*B4Ljt*@xth;Q@J={8| zWqP1BVLb;Xu#%mycGC%yBdP*R)=2#`O?Rca5j$yk-3*A2Z=@kH6vTzbAy>$OJv(UL z6++WBa1M}Xhz~04&`WK!A)31TnzfW&sr%mjQy77Q#zk*VVh!hNI0*~yfR3L<&5#|u z^7}8mQ5~~4R@N|1KHZK<;Nn$VIcR8v~A`6YVQHamv_OsIg`Zwz%Mg}U`NAb(WZOx2~~R8v)T zcH+ViZR^?7A}&)bB{4j*V{MdK8w$45@^Y}JjlLpFoId!50n&*EIuw`4T za{G!E8(!2E)io?&RTd>tZO}{9t8NsSAUB)SxrB35Ma`EzoZA$G*zmCRp-`t?;@Vl2 z=M4c)y``qg_fXAmUFOGE-w+ooi4biXp!j~Ho1%`{v^-eY@N48U?A-hT+A!yBj2Tc? zYDyc>#`{<)su?shK2tdpQjRTbRZBl>B|e*cQSsyq*E(>++LQ2r*tC!qbs{tQ={4nm zCv2myY@57h7iYWi)(3{TU7OsydSfdDwEGbNv`}RwGEv5M$jXQsJ-I)EvZOQNAGRP{F->e8T20qUXZL z&PYWWE7<=acCcZ^h50$PF{H6!M$T22$??)-cD%@pL4f;BQPUgcsGVKZ_?+k%O)e|I zqJgW!;9EE-?-WVtX>s^Vd$$2{aT~~T2Jzyk)zWTMQyy+T4${Ckt(XiUrj8EGRvw=o zUXMqk;X;PJW5in6pEakhTn{~t0(Ez@v?{aiW%(_@y$WC#P+nRHMyRhw3byU3!f=yi zTr~tKOBqLmXn57D?SxEAz6;PO?m~08TUSgTTG!#0q_HcnQ=w$ z3Y`9^dJu;Dc6EHATR-+#ZBFb=`ox{IZ~5jILVfFVu9mg+vCCSohUE(OJAi`+iEu{M zeXA>dwA9E#Jqx*H-Kzu7zaYF|^Eauy8^w1iZ5(L0q2i_9LfkWT3vQSdxkgdvgSj{@3ou0YTNygD2>yk1bsE!Ehl=4HWraf zB?#+{2ARsp*fScH2G;^6rX{Q8fRafU#usIFRjn?jJHwiAE3)r|?w z7K!gF#xc-k;;mS^gZPsAJ+X_h)E*Bul#61njwHnHKxHpG<0_Y?JmGjl}uQ%E)Wo}||Y z2Xr?y>ZTCMI$F!|W@@WcP1Y`xQcR>~c2P6aKXLF_&DAFWqDD;(jla=wxZ|t;NXbWK zBDSt{9$HyLj#UeZP;V;EV_;O$9H?0y;YxQt^VqYK)*)_Y%?3tUn}2quk%I9N*HqY-|D)vc2r=JPD#FOSxarJ--KY5yZmfHl zyLV0dinQN(S@{_HP9)-@77)yduB{j*z=L#p*L*vPhTdQ00xYP1Iu~mvnr(L&x6;jC zNWJZ1f&6BeWezp(+4?VmoQXNVu%)XjN ze%PS5>R?WFJ%lCo3@TEX9&b8HpB# zj!wkB>hR9x--QjX(*W?G@@h1K?UpU2^c!Fxn^-fssB~sfpRl7>cUZ_A2~RR;C@-gA z3Zo~6fB+&sww1hx`Yj>mqawiDTK`Y+ujuhv;)S0`VYPHdF1}-VGEuO&;WHh*(x1Y- zyp5if@y^Hbbw8h(z+%dda>QNvnq~l;`r;@QW%$jj+Y(y?@ua6p56LHWbz0_LV!*d- zLSC$>BWqGLzyV-I3f(a9%QeOq0*AVB*ARLQHP2A- zoAHADNvrSDEi(iqJ{D^4+!9DTpA4?Wj>^}6id@sW`W5a$+|!a-wR~3@pH|3D9!#TF zAj4t*Hc8jYnb(#ck~B!)5Z}ukb34JSW8XqyOBQCv*KaGu(sS*Uq_|a#a__c3?fc}? zug(jo45T5+@$pPrekqgBNxbXMss~eB8}x$=|vsp&KoJD zNen#>?)xYf`75AU=fO3!7klQgr&-$0=~Z!^w`|#u0g5VHVHWzCd(rb8K4$z`n2#A&`ma0cAN=|gQY-~nEi$F(#pW!x}LgP$N0QII=p}eIl@G=KC?vWo>`UA=*=gwPw^Tv&? zdEL;d7K~+mjRORbHLdtio8@v}?MqF*_KpD{G5ADU7AK*8Sj(7V$elZ|7`KS*)FbjT zZ4azX>p9c%3{QJECb9Fx%xJHoBrujdC~{32;L*T&ZX2oH7nwVC7e6gA*17wg4iNNS zWz5W^ILwYE1CXNU_|}Y6{N+|HJ4+oM5d;)b{izQIJcpf(%+@UAXIxibjb&)u+op>7 zKx4pwX{VlK86pgl&Gls0!)3tW){LG)ZF#@?_7@B~&Fy6Et#Zj7H9690-53d?n@5ax zq4ND~kSuB}cfxpsG`vh4LYK<*IAF5-l^`uq&_H1YH{<|R#fb5|3HPF>TFpwpi@m(-TVrLbwKfex^5ccj0FsdPJe(T58b)!0{-hv*b z)_@DWs>qsiJK;~7nI7g9=?lnu?}&TwgoPsAG%)FqGZo_DqcGEFu3L+~K_SCT*pkE1 zYTT-k3GRG$EU%OqkXUZo-pa`#WxPM+z8M#%t*M~Q9iu5wf*D`ZaVac~VforJaAa45 zm)3x3(6Dphh@cnVffb2J=`A9;p(M$Jhu8vVU_fb|EjgttsXS2iNDi2>_gifz@9+#@ zeSp}w)b<a6 z4J!Uc3&~s1d7KxHF6@W6NL0f!i398N*^0-LYil?7jeAHr_<4cgg*D2W_F}aX?-B`& zQ_ms_qAZX3CB^mp3u8bmt^{P5wb$d>ggd@Aa=4|e4__?q{+D!5uSoREp=*%`%xLnS zvB?Mu@4(&5mB<^lOW|s~{a5rOFD*tzKMvhb)uaeP7c9DeCpO}a;F;uKmC!o8^f{gu zYJZFTdjH9@31yV?s8wc2p9-+CsA}@1hO3%2lR(};X)D<=$Oi|&dzH+i(r++FDS#fi z6L?<21;JeX(prpElF~iAr!4zG9U=k%KkfmMaj&4_^V5IuWUj_XP-RsE$%0T(-RLhN1_Zo5iBft1b>kDyzDycRmu6BXX zKM=%v*hvIl2yBn{e6qN4hn%0gorD4c+_Rs~$@qdI9ZGeW zFd_nnP_viWuKunW-nmWZT{9kh7Xr?lUP{2!)VL=<>Pa7oc{{<6*pz+-1ajpcORq_a z$3x6|x>ox>9UdQsI!s9Xq(Z5@0~~y2{wmNY*7Uf!)_j`6(~|l#;>UVbLSQAw@wdL~ z+ybo;3^`tZ=WBo>vBs%@tlk)hidq7%_<$MhWla$UT!^3LcE888zm9vN zoj93;m)4j-H5WpEAevZ(Y5GQ-^Z9!x;4cdY3-LT&Wto2dhdk2DTKkiFms<<-n<{!J zgo1ufRh6DWJyTGxU?fopz?_{Kn1-SQQ&1$;Bo*wC&qX2=xVwkY5D*^75D@&o*r{s2 zO7uU9;%mqL42xOTEB`Um*%%Ft`pb#Qh1^qdFc&Ba{Lo6H_Wy@rtY+s@|I0Aa4vtC7 zG1JxU(9@4F3=GTFKC_LGP%ogt)0+OQS_Ld><@k5mD&S!&*ZW_~34tv`fNS%-fuoH4 z*)Sl!ze@q9e@%x{l=)ZLFUWr+z?J>8{gUVa+O-irFINN%ZzF^}0>}8o1-7@L{LUI{ zBZMM_1XA>n01?_>kpF+yzxt0R@Ug}l3hEiC+0OM0+&k8AC;}HOfp^M(fNn5gAPz7e z2A%PL9fc`$ThRe~`W{>_rhkNtVV{MtJ7}J@|6NiE0)pcoATq);(58d&8Tj7?IlvU; zzZxmfo`GY3H2%AE!e5m&-oqHrK>AL)XN};1;=kjGgX5yo{KbLKz)zfK;CMGGaI+KZ z8T?NG)qlVmB+p>4Zg_A!)!*R1aby11*8s+6a2N+6aK97fS@Yk>3lI>r|7eb7dj^kn zzyn8l(f%v&zw_q)BU9r|%Kt1t+fDVX`R|X<|J!mw;(vT5`h9(*cN0Qw%0B0fce6bU z|NYAi0)pZ{CcwuU?_+r|kmVWlFHip$RpV`}24?Nq$ba2s|1$3X7xYaH%)iq--{-)i z9zv)Gbs#*53%$RC9QBZN@QWkAgliDyrkaLj$+ z!JagOA9d{i2trmoTTQP2+Y;h+2)6>@W*bC=fOzu{SflD0EHe80vi@(^1O}1)13aw- W29MIAv4Yoy>BfM7u&?`7`u_k}&6ywo diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea3c445b0..afb76609c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jan 29 15:48:55 PST 2015 +#Thu May 03 13:47:38 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/gradlew b/gradlew index 91a7e269e..9aa616c27 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -161,4 +161,9 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 index aec99730b..e95643d6a --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/settings.gradle b/settings.gradle index 62c99d954..a04c21c0c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ rootProject.name='astyanax' -include 'astyanax-core', 'astyanax-queue', 'astyanax-thrift', 'astyanax-cql', 'astyanax-recipes', 'astyanax-cassandra', 'astyanax-entity-mapper', 'astyanax-examples', 'astyanax-contrib', 'astyanax-test', 'astyanax' +include 'astyanax', 'astyanax-core', 'astyanax-cassandra', 'astyanax-cassandra-all-shaded', + 'astyanax-thrift', 'astyanax-cql', 'astyanax-recipes', 'astyanax-queue', 'astyanax-entity-mapper', + 'astyanax-contrib', 'astyanax-examples', 'astyanax-test' From 3df76d204550ff7d458e8a086104d834716bf664 Mon Sep 17 00:00:00 2001 From: Jeremy Chapman Date: Tue, 12 Jun 2018 13:00:33 -0700 Subject: [PATCH 2/3] Correct README comment from DTCS to TWCS (TWC is much better and DTCS is deprecated in most Apache Cassandara 3.x releases) --- Readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.markdown b/Readme.markdown index ea5e34022..cf6ca04f4 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -46,7 +46,7 @@ Optional artifacts |GroupID/Org|ArtifactID/Name|Desc| | --------- | ------------- |----| |com.netflix.astyanax|astyanax-contrib|Optional integration with other commonly-used Netflix OSS modules.| -|com.netflix.astyanax|astyanax-queue|Queue implementation backed by Cassandra storage. Use at your own risk -- Cassandra can be a *very bad* storage engine for queues. If you insist on using Cassandra for queues, set a very short TTL and use DateTieredCompactionStrategy (DTCS).| +|com.netflix.astyanax|astyanax-queue|Queue implementation backed by Cassandra storage. Use at your own risk -- Cassandra can be a *very bad* storage engine for queues. If you insist on using Cassandra for queues, set a very short TTL and use TimeWindowCompactionStrategy (TWCS).| |com.netflix.astyanax|astyanax-entity-mapper|| |com.netflix.astyanax|astyanax-recipes|Optional implementations of some common patterns. Use at your own risk; some of these are popular but not well-suite for Cassandra for many use cases (looking at you, AllRowsReader).| From 8c869395d0bd3eb7b1e1db31a7bdd4ade59e625c Mon Sep 17 00:00:00 2001 From: Jeremy Chapman Date: Thu, 14 Jun 2018 14:46:36 -0700 Subject: [PATCH 3/3] Reflection-based ShadedTypeParser to minimize copy-paste (previous was more copy-paste with no reflection) --- .../db/marshal/ShadedTypeParser.java | 490 +++++++----------- 1 file changed, 180 insertions(+), 310 deletions(-) diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java index c41c7c1aa..9c73a4acc 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java @@ -17,271 +17,218 @@ import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.ConfigurationException; import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.SyntaxException; -import com.netflix.astyanax.shaded.org.apache.cassandra.utils.ByteBufferUtil; import com.netflix.astyanax.shaded.org.apache.cassandra.utils.FBUtilities; +import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.*; +import java.util.Map; /** - * Extend TypeParser to support shaded {@link AbstractType} from shaded package. - * Converts input class names to shaded package name. + * Extend {@link TypeParser} to support shaded {@link AbstractType} from shaded package. * - * Note that shading prefixes this class's package name with the shaded name {@link #SHADED_PREFIX}. - * - * Effectively uses {@link TypeParser} only as an interface. The implementation is a slightly altered copy-paste - * of {@link TypeParser} because {@link TypeParser#parse(String)} can't really be overridden. That method is public but - * references private fields (e.g. {@link TypeParser#idx}, etc.) and private methods - * ({@link TypeParser#getAbstractType}, etc.) which a derived class can't access. - * - * This is a very ugly way to support shading cassandra-all, but it's still the least invasive approach. + * This implementation uses some ugly reflection because {@link TypeParser#parse(String)} was apparently never meant + * to be overridden -- too many references to private fields (e.g. {@link TypeParser#idx}, etc.) and private methods + * ({@link TypeParser#getAbstractType}, etc.) which a derived method can't access. This ugliness could have been + * avoided if TypeParser hadn't tried to restrict extension by setting everything private instead of protected. */ public class ShadedTypeParser extends TypeParser { - public static final String SHADED_PREFIX = "com.netflix.astyanax.shaded."; - protected final String str; - protected int idx; - protected static final Map> cache = new HashMap(); - protected static final TypeParser EMPTY_PARSER = new ShadedTypeParser(""); + private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(ShadedTypeParser.class); - public static ShadedTypeParser buildTypeParser(String str, int idx){ - return new ShadedTypeParser(str, idx); - } + public static final String SHADED_PREFIX = "com.netflix.astyanax.shaded."; - public ShadedTypeParser(String str) { - // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor - super(str); - this.str = str; - this.idx = 0; - } + protected static TypeParser EMPTY_PARSER; - public ShadedTypeParser(String str, int idx) { - // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor - super(str); - this.str = str; - this.idx = idx; - } - - public static String getShadedClassName(String className){ - if(className.startsWith(SHADED_PREFIX)){ - return className; - } else if (className.contains(".")){ - return SHADED_PREFIX + className; - } else { - return SHADED_PREFIX + "org.apache.cassandra.db.marshal." + className; + static { + try { + EMPTY_PARSER = new ShadedTypeParser(""); + } catch (ConfigurationException e) { + Logger.error("ShadedTypeParser failed to create EMPTY_PARSER; message=" + e.getMessage(), e); } } - public static String getShadedTypeName(String typeName){ - if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { - return typeName.substring( "org.apache.cassandra.db.marshal.".length() ); - } else if ( typeName.startsWith( SHADED_PREFIX + "org.apache.cassandra.db.marshal." ) ) { - return typeName.substring( (SHADED_PREFIX + "org.apache.cassandra.db.marshal.").length() ); - } - return typeName; - } + /******************************************************************************************************************* + * Begin ugly reflection required to work around TypeParser's lack of design for extension: + ******************************************************************************************************************/ + protected static Field strField; + protected static Field idxField; + protected static Field cacheField; + protected static Map> cache; + protected static Method skipBlankMethod; + protected static Method skipBlankMethod2; + protected static Method isEOSMethod; + protected static Method isEOSMethod2; + protected static Method isIdentifierCharMethod; + + protected static void init() throws ConfigurationException { + try { + strField = ShadedTypeParser.class.getSuperclass().getDeclaredField("str"); + strField.setAccessible(true); - public static AbstractType parse(String str) throws SyntaxException, ConfigurationException { - if (str == null) { - return BytesType.instance; - } else { - str = getShadedClassName(str); - AbstractType type = (AbstractType)cache.get(str); - if (type != null) { - return type; - } else { - int i = 0; - i = skipBlank(str, i); + idxField = ShadedTypeParser.class.getSuperclass().getDeclaredField("idx"); + idxField.setAccessible(true); - int j; - for(j = i; !isEOS(str, i) && isIdentifierChar(str.charAt(i)); ++i) { - ; - } + cacheField = ShadedTypeParser.class.getSuperclass().getDeclaredField("cache"); + cacheField.setAccessible(true); + cache = (Map>)cacheField.get(null); - if (i == j) { - return BytesType.instance; - } else { - String name = str.substring(j, i); - name = getShadedClassName(name); - i = skipBlank(str, i); - if (!isEOS(str, i) && str.charAt(i) == '(') { - ShadedTypeParser typeParser = buildTypeParser(str, i); - type = getAbstractType(name, typeParser); - } else { - type = getAbstractType(name); - } + skipBlankMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("skipBlank"); + skipBlankMethod.setAccessible(true); - cache.put(str, type); - return type; - } - } - } - } + skipBlankMethod2 = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("skipBlank", String.class, int.class); + skipBlankMethod2.setAccessible(true); - public AbstractType parse() throws SyntaxException, ConfigurationException { - this.skipBlank(); - String name = this.readNextIdentifier(); - name = getShadedClassName(name); - this.skipBlank(); - return !this.isEOS() && this.str.charAt(this.idx) == '(' ? getAbstractType(name, this) : getAbstractType(name); - } + isEOSMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isEOS"); + isEOSMethod.setAccessible(true); - public Map getKeyValueParameters() throws SyntaxException { - Map map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; - - String k; - String v; - for(; this.skipBlankAndComma(); map.put(k, v)) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } + isEOSMethod2 = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isEOS", String.class, int.class); + isEOSMethod2.setAccessible(true); - k = this.readNextIdentifier(); - v = ""; - this.skipBlank(); - if (this.str.charAt(this.idx) == '=') { - ++this.idx; - this.skipBlank(); - v = this.readNextIdentifier(); - } else if (this.str.charAt(this.idx) != ',' && this.str.charAt(this.idx) != ')') { - this.throwSyntaxError("unexpected character '" + this.str.charAt(this.idx) + "'"); - } - } + isIdentifierCharMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isIdentifierChar", int.class); + isIdentifierCharMethod.setAccessible(true); - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser init() failed for reflection; message=" + e.getMessage(), e); } } - public List> getTypeParameters() throws SyntaxException, ConfigurationException { - List> list = new ArrayList(); - if (this.isEOS()) { - return list; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; + protected String getStr() throws IllegalAccessException { + return (String)strField.get(this); + } - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return list; - } + protected int getIdx() throws IllegalAccessException { + return idxField.getInt(this); + } - try { - list.add(this.parse()); - } catch (SyntaxException var4) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var4); - throw ex; - } - } + protected void setIdx(int idx) throws IllegalAccessException { + idxField.setInt(this, idx); + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); - } + protected void skipBlank() throws InvocationTargetException, IllegalAccessException { + skipBlankMethod.invoke(this); } - public Map> getAliasParameters() throws SyntaxException, ConfigurationException { - Map> map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; + protected static int skipBlank(String str, int i) throws InvocationTargetException, IllegalAccessException { + return (Integer)skipBlankMethod2.invoke(null, str, i); + } - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } + protected boolean isEOS() throws InvocationTargetException, IllegalAccessException { + return (Boolean)isEOSMethod.invoke(this); + } - String alias = this.readNextIdentifier(); - if (alias.length() != 1) { - this.throwSyntaxError("An alias should be a single character"); - } + protected static boolean isEOS(String str, int i) throws InvocationTargetException, IllegalAccessException { + return (Boolean)isEOSMethod2.invoke(null, str, i); + } - char aliasChar = alias.charAt(0); - if (aliasChar < '!' || aliasChar > 127) { - this.throwSyntaxError("An alias should be a single character in [0..9a..bA..B-+._&]"); - } + private static boolean isIdentifierChar(int c) throws InvocationTargetException, IllegalAccessException { + return (Boolean)isIdentifierCharMethod.invoke(null, c); + } - this.skipBlank(); - if (this.str.charAt(this.idx) != '=' || this.str.charAt(this.idx + 1) != '>') { - this.throwSyntaxError("expecting '=>' token"); - } + protected static Method getRawAbstractTypeMethod(Class typeClass) throws NoSuchMethodException { + return ShadedTypeParser.class.getSuperclass().getDeclaredMethod("getRawAbstractType", + typeClass, EMPTY_PARSER.getClass()); + } - this.idx += 2; - this.skipBlank(); + /******************************************************************************************************************* + * End ugly reflection required to work around TypeParser's lack of design for extension + ******************************************************************************************************************/ - try { - map.put((byte)aliasChar, this.parse()); - } catch (SyntaxException var6) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var6); - throw ex; - } - } + public static ShadedTypeParser buildTypeParser(String str, int idx) throws ConfigurationException { + return new ShadedTypeParser(str, idx); + } + + public ShadedTypeParser(String str) throws ConfigurationException { + this(str, 0); + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + public ShadedTypeParser(String str, int idx) throws ConfigurationException { + super(str); + init(); + try { + setIdx(idx); + } catch (IllegalAccessException e) { + throw new ConfigurationException( + "ShadedTypeParser constructor failed for reflection; message=" + e.getMessage(), e); } } - public Map getCollectionsParameters() throws SyntaxException, ConfigurationException { - Map map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); + public static String getShadedClassName(String className){ + if(className.startsWith(SHADED_PREFIX)){ + return className; + } else if (className.contains(".")){ + return SHADED_PREFIX + className; } else { - ++this.idx; - - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } - - String bbHex = this.readNextIdentifier(); - ByteBuffer bb = null; - - try { - bb = ByteBufferUtil.hexToBytes(bbHex); - } catch (NumberFormatException var7) { - this.throwSyntaxError(var7.getMessage()); - } + return SHADED_PREFIX + "org.apache.cassandra.db.marshal." + className; + } + } - this.skipBlank(); - if (this.str.charAt(this.idx) != ':') { - this.throwSyntaxError("expecting ':' token"); - } + public static String getShadedTypeName(String typeName){ + if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( "org.apache.cassandra.db.marshal.".length() ); + } else if ( typeName.startsWith( SHADED_PREFIX + "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( (SHADED_PREFIX + "org.apache.cassandra.db.marshal.").length() ); + } + return typeName; + } - ++this.idx; - this.skipBlank(); + /******************************************************************************************************************* + * Begin methods very slightly modified from {@link TypeParser} to support shaded classes + ******************************************************************************************************************/ + public static AbstractType parse(String str) throws SyntaxException, ConfigurationException { + try{ + if (str == null) { + return BytesType.instance; + } else { + str = getShadedClassName(str); + AbstractType type = null; + type = (AbstractType) cache.get(str); + if (type != null) { + return type; + } else { + int i = 0; + i = skipBlank(str, i); - try { - AbstractType type = this.parse(); - if (!(type instanceof CollectionType)) { - throw new SyntaxException(type.toString() + " is not a collection type"); + int j; + for (j = i; !isEOS(str, i) && isIdentifierChar(str.charAt(i)); ++i) { + ; } - map.put(bb, (CollectionType)type); - } catch (SyntaxException var6) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var6); - throw ex; + if (i == j) { + return BytesType.instance; + } else { + String name = str.substring(j, i); + name = getShadedClassName(name); + i = skipBlank(str, i); + if (!isEOS(str, i) && str.charAt(i) == '(') { + ShadedTypeParser typeParser = buildTypeParser(str, i); + type = getAbstractType(name, typeParser); + } else { + type = getAbstractType(name); + } + + cache.put(str, type); + return type; + } } } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new ConfigurationException( + "ShadedTypeParser parse(String) failed for reflection; message=" + e.getMessage(), e); + } + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + public AbstractType parse() throws SyntaxException, ConfigurationException { + try { + skipBlank(); + String name = this.readNextIdentifier(); + name = getShadedClassName(name); + skipBlank(); + return !isEOS() && this.getStr().charAt(getIdx()) == '(' ? getAbstractType(name, this) : getAbstractType(name); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new ConfigurationException( + "ShadedTypeParser parse() failed for reflection; message=" + e.getMessage(), e); } } @@ -292,10 +239,14 @@ protected static AbstractType getAbstractType(String compareWith) throws Conf try { Field field = typeClass.getDeclaredField("instance"); return (AbstractType)field.get((Object)null); - } catch (NoSuchFieldException var4) { - return getRawAbstractType(typeClass, EMPTY_PARSER); - } catch (IllegalAccessException var5) { - return getRawAbstractType(typeClass, EMPTY_PARSER); + } catch (NoSuchFieldException | IllegalAccessException var4) { + try { + Method getRawAbstractTypeMethod = getRawAbstractTypeMethod(typeClass); + return (AbstractType) getRawAbstractTypeMethod.invoke(null, typeClass, EMPTY_PARSER); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser getAbstractType failed for reflection; message=" + e.getMessage(), e); + } } } @@ -307,11 +258,14 @@ protected static AbstractType getAbstractType(String compareWith, TypeParser try { Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); return (AbstractType)method.invoke((Object)null, parser); - } catch (NoSuchMethodException var6) { - type = getRawAbstractType(typeClass); - return AbstractType.parseDefaultParameters(type, parser); - } catch (IllegalAccessException var7) { - type = getRawAbstractType(typeClass); + } catch (NoSuchMethodException | IllegalAccessException var6) { + try { + Method getRawAbstractTypeMethod = getRawAbstractTypeMethod(typeClass); + type = (AbstractType) getRawAbstractTypeMethod.invoke(null, typeClass); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser getAbstractType() failed for reflection; message=" + e.getMessage(), e); + } return AbstractType.parseDefaultParameters(type, parser); } catch (InvocationTargetException var8) { ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); @@ -320,92 +274,8 @@ protected static AbstractType getAbstractType(String compareWith, TypeParser } } - protected static AbstractType getRawAbstractType(Class> typeClass) throws ConfigurationException { - try { - Field field = typeClass.getDeclaredField("instance"); - return (AbstractType)field.get((Object)null); - } catch (NoSuchFieldException var2) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (IllegalAccessException var3) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } - } - - protected static AbstractType getRawAbstractType(Class> typeClass, TypeParser parser) throws ConfigurationException { - try { - Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); - return (AbstractType)method.invoke((Object)null, parser); - } catch (NoSuchMethodException var4) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (IllegalAccessException var5) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (InvocationTargetException var6) { - ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); - ex.initCause(var6.getTargetException()); - throw ex; - } - } - - protected void throwSyntaxError(String msg) throws SyntaxException { - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: %s", this.str, this.idx, msg)); - } - - protected boolean isEOS() { - return isEOS(this.str, this.idx); - } - - protected static boolean isEOS(String str, int i) { - return i >= str.length(); - } - - protected static boolean isBlank(int c) { - return c == 32 || c == 9 || c == 10; - } - - protected void skipBlank() { - this.idx = skipBlank(this.str, this.idx); - } + /******************************************************************************************************************* + * End methods copy-pasted from {@link TypeParser} and modified slightly to to work with shaded classes + ******************************************************************************************************************/ - protected static int skipBlank(String str, int i) { - while(!isEOS(str, i) && isBlank(str.charAt(i))) { - ++i; - } - - return i; - } - - protected boolean skipBlankAndComma() { - for(boolean commaFound = false; !this.isEOS(); ++this.idx) { - int c = this.str.charAt(this.idx); - if (c == ',') { - if (commaFound) { - return true; - } - - commaFound = true; - } else if (!isBlank(c)) { - return true; - } - } - - return false; - } - - protected static boolean isIdentifierChar(int c) { - return c >= 48 && c <= 57 || c >= 97 && c <= 122 || c >= 65 && c <= 90 || c == 45 || c == 43 || c == 46 || c == 95 || c == 38; - } - - public String readNextIdentifier() { - int i; - for(i = this.idx; !this.isEOS() && isIdentifierChar(this.str.charAt(this.idx)); ++this.idx) { - ; - } - - return this.str.substring(i, this.idx); - } - - public char readNextChar() { - this.skipBlank(); - return this.str.charAt(this.idx++); - } }