Skip to content

Commit b82a959

Browse files
committed
Datastax DSE a Separate Module fix #653
1 parent 7c9f59b commit b82a959

5 files changed

Lines changed: 217 additions & 6 deletions

File tree

coverage-report/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,13 @@
798798
<version>2.0.7</version>
799799
</dependency>
800800

801+
<dependency>
802+
<groupId>com.datastax.cassandra</groupId>
803+
<artifactId>dse-driver</artifactId>
804+
<version>1.1.2</version>
805+
<scope>test</scope>
806+
</dependency>
807+
801808
</dependencies>
802809

803810
</project>

jooby-cassandra/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@
119119
<scope>test</scope>
120120
</dependency>
121121

122+
<dependency>
123+
<groupId>com.datastax.cassandra</groupId>
124+
<artifactId>dse-driver</artifactId>
125+
<version>1.1.2</version>
126+
<scope>test</scope>
127+
</dependency>
128+
122129
</dependencies>
123130

124131
</project>

jooby-cassandra/src/main/java/org/jooby/cassandra/Cassandra.java

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.function.BiConsumer;
2626
import java.util.function.Consumer;
27+
import java.util.function.Supplier;
2728

2829
import org.jooby.Env;
2930
import org.jooby.Env.ServiceKey;
@@ -190,6 +191,21 @@
190191
* The accessor can be required or injected in a MVC route.
191192
* </p>
192193
*
194+
* <h2>dse-driver</h2>
195+
* <p>
196+
* Add the <code>dse-driver</code> dependency to your classpath and then:
197+
* </p>
198+
*
199+
* <pre>
200+
* {
201+
* use(new Cassandra(DseCluster::build));
202+
* }
203+
* </pre>
204+
*
205+
* <p>
206+
* That's all! Now you can <code>require/inject</code> a <code>DseSession</code>.
207+
* </p>
208+
*
193209
* <h2>async</h2>
194210
* <p>
195211
* Async? Of course!!! just use the Datastax async API:
@@ -269,6 +285,8 @@ public class Cassandra implements Module {
269285
/** The logging system. */
270286
private final Logger log = LoggerFactory.getLogger(getClass());
271287

288+
private static final Supplier<Cluster.Builder> BUILDER = Cluster::builder;
289+
272290
private BiConsumer<Cluster.Builder, Config> ccbuilder;
273291

274292
private BiConsumer<Cluster, Config> cc;
@@ -279,6 +297,35 @@ public class Cassandra implements Module {
279297

280298
private List<Class> accesors = new ArrayList<>();
281299

300+
private Supplier<Cluster.Builder> builder = Cluster::builder;
301+
302+
/**
303+
* Creates a new {@link Cassandra} module.
304+
*
305+
* Conenct via connection string:
306+
*
307+
* <pre>{@code
308+
* {
309+
* use(new Cassandra("cassandra://localhost/db", DseCluster::builder));
310+
* }
311+
* }</pre>
312+
*
313+
* Or via property:
314+
*
315+
* <pre>{@code
316+
* {
317+
* use(new Cassandra("db", DseCluster::builder));
318+
* }
319+
* }</pre>
320+
*
321+
* @param db Connection string or a property with a connection String.
322+
* @param builder Cluster builder.
323+
*/
324+
public Cassandra(final String db, final Supplier<Cluster.Builder> builder) {
325+
this.db = requireNonNull(db, "ContactPoint/db key required.");
326+
this.builder = requireNonNull(builder, "Cluster.Builder required.");
327+
}
328+
282329
/**
283330
* Creates a new {@link Cassandra} module.
284331
*
@@ -301,7 +348,15 @@ public class Cassandra implements Module {
301348
* @param db Connection string or a property with a connection String.
302349
*/
303350
public Cassandra(final String db) {
304-
this.db = requireNonNull(db, "ContactPoint/db key required.");
351+
this(db, BUILDER);
352+
}
353+
354+
/**
355+
* Creates a new {@link Cassandra} module. A property <code>db</code> property must be present and
356+
* have a valid connection string.
357+
*/
358+
public Cassandra(final Supplier<Cluster.Builder> builder) {
359+
this("db", builder);
305360
}
306361

307362
/**
@@ -382,7 +437,7 @@ public void configure(final Env env, final Config conf, final Binder binder) {
382437
return null;
383438
};
384439

385-
Cluster.Builder builder = Cluster.builder()
440+
Cluster.Builder builder = this.builder.get()
386441
.addContactPoints(cstr.contactPoints())
387442
.withPort(cstr.port());
388443

@@ -409,11 +464,11 @@ public void configure(final Env env, final Config conf, final Binder binder) {
409464
LocalDateCodec.instance,
410465
LocalTimeCodec.instance);
411466

412-
bind.apply(Cluster.class, cstr.keyspace(), cluster);
467+
hierarchy(cluster.getClass(), type -> bind.apply(type, cstr.keyspace(), cluster));
413468

414469
/** Session + Mapper */
415470
Session session = cluster.connect(cstr.keyspace());
416-
bind.apply(Session.class, cstr.keyspace(), session);
471+
hierarchy(session.getClass(), type -> bind.apply(type, cstr.keyspace(), session));
417472

418473
MappingManager manager = new MappingManager(session);
419474
bind.apply(MappingManager.class, cstr.keyspace(), manager);
@@ -439,4 +494,19 @@ public void configure(final Env env, final Config conf, final Binder binder) {
439494
});
440495
}
441496

497+
@SuppressWarnings("rawtypes")
498+
static void hierarchy(final Class type, final Consumer<Class> consumer) {
499+
if (type != Object.class) {
500+
if (type.getName().startsWith("com.datastax")) {
501+
consumer.accept(type);
502+
}
503+
for (Class i : type.getInterfaces()) {
504+
hierarchy(i, consumer);
505+
}
506+
if (!type.isInterface()) {
507+
hierarchy(type.getSuperclass(), consumer);
508+
}
509+
}
510+
}
511+
442512
}

jooby-cassandra/src/test/java/org/jooby/cassandra/CassandraTest.java

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import static org.easymock.EasyMock.expect;
44
import static org.easymock.EasyMock.expectLastCall;
5+
import static org.junit.Assert.assertEquals;
6+
7+
import java.util.HashSet;
8+
import java.util.Set;
59

610
import org.jooby.Env;
711
import org.jooby.Env.ServiceKey;
@@ -17,12 +21,16 @@
1721
import com.datastax.driver.core.Cluster.Builder;
1822
import com.datastax.driver.core.CodecRegistry;
1923
import com.datastax.driver.core.Configuration;
24+
import com.datastax.driver.core.DelegatingCluster;
2025
import com.datastax.driver.core.Host.StateListener;
2126
import com.datastax.driver.core.Session;
27+
import com.datastax.driver.dse.DseCluster;
28+
import com.datastax.driver.dse.DseSession;
2229
import com.datastax.driver.extras.codecs.jdk8.InstantCodec;
2330
import com.datastax.driver.extras.codecs.jdk8.LocalDateCodec;
2431
import com.datastax.driver.extras.codecs.jdk8.LocalTimeCodec;
2532
import com.datastax.driver.mapping.MappingManager;
33+
import com.google.common.collect.ImmutableSet;
2634
import com.google.inject.Binder;
2735
import com.google.inject.Key;
2836
import com.google.inject.binder.AnnotatedBindingBuilder;
@@ -36,8 +44,15 @@
3644
MappingManager.class, Datastore.class, CassandraMapper.class })
3745
public class CassandraTest {
3846

47+
@SuppressWarnings({"rawtypes", "unchecked" })
3948
private Block clusterBuilder = unit -> {
49+
String cname = Cluster.class.getName();
4050
unit.mockStatic(Cluster.class);
51+
expect(Cluster.class.getName()).andReturn(cname);
52+
expect(Cluster.class.getInterfaces()).andReturn(new Class[0]);
53+
expect(Cluster.class.isInterface()).andReturn(false);
54+
Class obj = Object.class;
55+
expect(Cluster.class.getSuperclass()).andReturn(obj);
4156

4257
Cluster cluster = unit.get(Cluster.class);
4358
expect(cluster.getConfiguration()).andReturn(unit.get(Configuration.class));
@@ -47,6 +62,23 @@ public class CassandraTest {
4762
expect(builder.build()).andReturn(cluster);
4863
};
4964

65+
@SuppressWarnings({"rawtypes", "unchecked" })
66+
private Block clusterBuilderProvider = unit -> {
67+
String cname = Cluster.class.getName();
68+
unit.mockStatic(Cluster.class);
69+
expect(Cluster.class.getName()).andReturn(cname);
70+
expect(Cluster.class.getInterfaces()).andReturn(new Class[0]);
71+
expect(Cluster.class.isInterface()).andReturn(false);
72+
Class obj = Object.class;
73+
expect(Cluster.class.getSuperclass()).andReturn(obj);
74+
75+
Cluster cluster = unit.get(Cluster.class);
76+
expect(cluster.getConfiguration()).andReturn(unit.get(Configuration.class));
77+
78+
Builder builder = unit.get(Cluster.Builder.class);
79+
expect(builder.build()).andReturn(cluster);
80+
};
81+
5082
private Block codecRegistry = unit -> {
5183
CodecRegistry codecRegistry = unit.powerMock(CodecRegistry.class);
5284

@@ -124,6 +156,37 @@ public void connectViaProperty() throws Exception {
124156
});
125157
}
126158

159+
@Test
160+
public void connectViaPropertySupplier() throws Exception {
161+
new MockUnit(Env.class, Config.class, Binder.class, Cluster.class, Cluster.Builder.class,
162+
Configuration.class, Session.class)
163+
.expect(unit -> {
164+
Config conf = unit.get(Config.class);
165+
expect(conf.getString("db")).andReturn("cassandra://localhost/beers");
166+
})
167+
.expect(serviceKey(new Env.ServiceKey()))
168+
.expect(clusterBuilderProvider)
169+
.expect(contactPoints("localhost"))
170+
.expect(port(9042))
171+
.expect(codecRegistry)
172+
.expect(bind("beers", Cluster.class))
173+
.expect(bind(null, Cluster.class))
174+
.expect(bind("beers", Session.class))
175+
.expect(bind(null, Session.class))
176+
.expect(connect("beers"))
177+
.expect(mapper)
178+
.expect(bind("beers", MappingManager.class))
179+
.expect(bind(null, MappingManager.class))
180+
.expect(datastore)
181+
.expect(bind("beers", Datastore.class))
182+
.expect(bind(null, Datastore.class))
183+
.expect(routeMapper).expect(onStop)
184+
.run(unit -> {
185+
new Cassandra(() -> unit.get(Cluster.Builder.class))
186+
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
187+
});
188+
}
189+
127190
private Block serviceKey(final ServiceKey serviceKey) {
128191
return unit -> {
129192
Env env = unit.get(Env.class);
@@ -158,6 +221,33 @@ public void connectViaConnectionString() throws Exception {
158221
});
159222
}
160223

224+
@Test
225+
public void connectViaConnectionStringSupplier() throws Exception {
226+
new MockUnit(Env.class, Config.class, Binder.class, Cluster.class, Cluster.Builder.class,
227+
Configuration.class, Session.class)
228+
.expect(clusterBuilderProvider)
229+
.expect(serviceKey(new Env.ServiceKey()))
230+
.expect(contactPoints("localhost"))
231+
.expect(port(9042))
232+
.expect(codecRegistry)
233+
.expect(bind("beers", Cluster.class))
234+
.expect(bind(null, Cluster.class))
235+
.expect(bind("beers", Session.class))
236+
.expect(bind(null, Session.class))
237+
.expect(connect("beers"))
238+
.expect(mapper)
239+
.expect(bind("beers", MappingManager.class))
240+
.expect(bind(null, MappingManager.class))
241+
.expect(datastore)
242+
.expect(bind("beers", Datastore.class))
243+
.expect(bind(null, Datastore.class))
244+
.expect(routeMapper).expect(onStop)
245+
.run(unit -> {
246+
new Cassandra("cassandra://localhost/beers", () -> unit.get(Cluster.Builder.class))
247+
.configure(unit.get(Env.class), unit.get(Config.class), unit.get(Binder.class));
248+
});
249+
}
250+
161251
@Test
162252
public void onStop() throws Exception {
163253
new MockUnit(Env.class, Config.class, Binder.class, Cluster.class, Cluster.Builder.class,
@@ -350,16 +440,41 @@ private Block bind(final String name, final Class type) {
350440
Binder binder = unit.get(Binder.class);
351441
if (name == null) {
352442
AnnotatedBindingBuilder aab = unit.mock(AnnotatedBindingBuilder.class);
353-
aab.toInstance(unit.get(type));
443+
Object val = unit.get(type);
444+
aab.toInstance(val);
445+
expectLastCall().times(1, 2);
354446
expect(binder.bind(Key.get(type))).andReturn(aab);
447+
expect(binder.bind(Key.get(val.getClass()))).andReturn(aab).times(0, 1);
355448
} else {
356449
AnnotatedBindingBuilder aab = unit.mock(AnnotatedBindingBuilder.class);
357-
aab.toInstance(unit.get(type));
450+
Object val = unit.get(type);
451+
aab.toInstance(val);
452+
expectLastCall().times(1, 2);
358453
expect(binder.bind(Key.get(type, Names.named(name)))).andReturn(aab);
454+
expect(binder.bind(Key.get(val.getClass(), Names.named(name)))).andReturn(aab).times(0, 1);
359455
}
360456
};
361457
}
362458

459+
@Test
460+
public void hierarchy() {
461+
assertEquals(ImmutableSet.of(), hierarchy(Object.class));
462+
// normal
463+
assertEquals(ImmutableSet.of(Cluster.class), hierarchy(Cluster.class));
464+
assertEquals(ImmutableSet.of(Session.class), hierarchy(Session.class));
465+
// dse
466+
assertEquals(ImmutableSet.of(DseCluster.class, DelegatingCluster.class, Cluster.class),
467+
hierarchy(DseCluster.class));
468+
assertEquals(ImmutableSet.of(DseSession.class, Session.class), hierarchy(DseSession.class));
469+
}
470+
471+
@SuppressWarnings("rawtypes")
472+
private Set<Class> hierarchy(final Class type) {
473+
Set<Class> types = new HashSet<>();
474+
Cassandra.hierarchy(type, types::add);
475+
return types;
476+
}
477+
363478
private Block port(final int port) {
364479
return unit -> {
365480
Builder builder = unit.get(Cluster.Builder.class);

md/doc/cassandra/cassandra.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ Accessors provide a way to map custom queries not supported by the default entit
119119

120120
The accessor can be required or injected in a MVC route.
121121

122+
## dse-driver
123+
124+
Add the `dse-driver` dependency to your classpath and then:
125+
126+
```java
127+
{
128+
use(new Cassandra(DseCluster::build));
129+
}
130+
```
131+
132+
That's all! Now you can `require/inject` a `DseSession`.
133+
122134
## async
123135

124136
Async? Of course!!! just use the Datastax async API:

0 commit comments

Comments
 (0)