DATACASS-287 - Polishing.

Move test listeners into a common support package. Hide constructors and introduce factory methods. Encapsulate fields. Add volatile modifier to fields to prevent visibility issues. Enhance JavaDoc on CassandraOperations.
This commit is contained in:
Mark Paluch
2016-06-03 15:15:05 +02:00
committed by John Blum
parent b14956cb67
commit 76f3d32360
12 changed files with 447 additions and 248 deletions

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.support;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Convenient listener base class that includes a {@link CountDownLatch} in order to test asynchronous behavior.
*
* @author Matthew T. Adams
*/
public class TestListener {
protected CountDownLatch latch;
public TestListener() {
this(1);
}
public TestListener(int latchCount) {
latch = new CountDownLatch(latchCount);
}
public void await() throws InterruptedException {
latch.await();
}
public void await(long ms) throws InterruptedException {
latch.await(ms, TimeUnit.MILLISECONDS);
}
public void countDown() {
latch.countDown();
}
}

View File

@@ -16,7 +16,7 @@
package org.springframework.cassandra.test.integration.core;
import org.springframework.cassandra.core.AsynchronousQueryListener;
import org.springframework.cassandra.support.TestListener;
import org.springframework.cassandra.test.integration.support.CallbackSynchronizationSupport;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
@@ -26,8 +26,9 @@ import com.datastax.driver.core.Row;
*
* @author David Webb
* @author Matthew T. Adams
* @author Mark Paluch
*/
public class BookListener extends TestListener implements AsynchronousQueryListener {
public class BookListener extends CallbackSynchronizationSupport implements AsynchronousQueryListener {
private Book book;
private boolean done;

View File

@@ -41,6 +41,10 @@ import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.RetryPolicy;
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.cassandra.test.integration.support.QueryListener;
import org.springframework.cassandra.test.integration.support.ListOfMapListener;
import org.springframework.cassandra.test.integration.support.MapListener;
import org.springframework.cassandra.test.integration.support.ObjectListener;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -135,7 +139,7 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
/**
* Tests that test {@link AsynchronousQueryListener} should create an anonymous subclass of this class then call
* either {@link #test()} or {@link #test(int)}
* {@link #test()}.
*/
abstract class AsynchronousQueryListenerTestTemplate {
@@ -143,22 +147,22 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
* to the appropriate value before returning.
*/
abstract void doAsyncQuery(Book b, BasicListener listener);
abstract void doAsyncQuery(Book b, QueryListener listener);
void test() throws InterruptedException {
Book expected = insert(1)[0];
BasicListener listener = new BasicListener();
QueryListener listener = QueryListener.create();
doAsyncQuery(expected, listener);
listener.await();
Row r = cqlOperations.getResultSetUninterruptibly(listener.rsf).one();
Row r = cqlOperations.getResultSetUninterruptibly(listener.getResultSetFuture()).one();
Book actual = new Book(r.getString(0), r.getString(1));
assertBook(expected, actual);
}
}
/**
* Tests that test {@link QueryForObjectListener} should create an anonymous subclass of this class then call either
* {@link #test()} or {@link #test(int)}
* Tests that test {@link QueryForObjectListener} should create an anonymous subclass of this class then call
* {@link #test()}
*/
abstract class QueryForObjectListenerTestTemplate<T> {
@@ -172,19 +176,19 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
void test() throws Exception {
Book book = insert(1)[0];
ObjectListener<T> listener = new ObjectListener<T>();
ObjectListener<T> listener = ObjectListener.create();
doAsyncQuery(book, listener);
listener.await();
if (listener.exception != null) {
throw listener.exception;
if (listener.getException() != null) {
throw listener.getException();
}
assertEquals(expected, listener.result);
assertEquals(expected, listener.getResult());
}
}
/**
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call either
* {@link #test()} or {@link #test(int)}
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call
* {@link #test()}
*/
abstract class QueryForMapListenerTestTemplate {
@@ -198,19 +202,18 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
void test() throws Exception {
Book book = insert(1)[0];
MapListener listener = new MapListener();
MapListener listener = MapListener.create();
doAsyncQuery(book, listener);
listener.await();
if (listener.exception != null) {
throw listener.exception;
if (listener.getException() != null) {
throw listener.getException();
}
assertMapEquals(expected, listener.result);
assertMapEquals(expected, listener.getResult());
}
}
/**
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call either
* {@link #test()} or {@link #test(int)}
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call or {@link #test(int)}
*/
abstract class QueryForListListenerTestTemplate {
@@ -224,19 +227,19 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
void test(int n) throws Exception {
Book[] books = insert(n);
ListOfMapListener listener = new ListOfMapListener();
ListOfMapListener listener = ListOfMapListener.create();
Arrays.sort(books, BOOK_COMPARATOR);
doAsyncQuery(books, listener);
listener.await();
if (listener.exception != null) {
throw listener.exception;
if (listener.getException() != null) {
throw listener.getException();
}
// sort results the same way as the books array above
Collections.sort(listener.result, MAP_WITH_ISBN_COMPARATOR);
Collections.sort(listener.getResult(), MAP_WITH_ISBN_COMPARATOR);
for (int i = 0; i < expected.size(); i++) {
assertMapEquals(expected.get(i), listener.result.get(i));
assertMapEquals(expected.get(i), listener.getResult().get(i));
}
}
}
@@ -245,7 +248,7 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
public void testString_AsynchronousQueryListener_Cancelled() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, BasicListener listener) {
void doAsyncQuery(Book b, QueryListener listener) {
Cancellable qc = cqlOperations.queryAsynchronously(cql(b), listener);
qc.cancel();
}
@@ -256,7 +259,7 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
public void testString_AsynchronousQueryListener() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, BasicListener listener) {
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener);
}
}.test();
@@ -265,7 +268,7 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
public void testString_AsynchronousQueryListener_QueryOptions(final ConsistencyLevel cl) throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, BasicListener listener) {
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener, new QueryOptions(cl, RetryPolicy.LOGGING));
}
}.test();
@@ -285,7 +288,7 @@ public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceC
public void testSelect_AsynchronousQueryListener() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, BasicListener listener) {
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener);
}
}.test();

View File

@@ -1,25 +0,0 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.core.async;
import java.util.Map;
import org.springframework.cassandra.core.QueryForListOfMapListener;
/**
* @author Matthew T. Adams
*/
public class ListOfMapListener extends ListListener<Map<String, Object>> implements QueryForListOfMapListener {}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.support;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.springframework.util.Assert;
/**
* Convenient listener base class that includes a {@link CountDownLatch} in order to test asynchronous behavior. This
* class can be extended
*
* @author Matthew T. Adams
* @author Mark Paluch
*/
public abstract class CallbackSynchronizationSupport {
private final CountDownLatch latch;
/**
* Creates a new {@link CallbackSynchronizationSupport}
*/
protected CallbackSynchronizationSupport() {
this(1);
}
/**
* Creates a new {@link CallbackSynchronizationSupport} for a given {@code latchCount} of callbacks.
*
* @param latchCount {@link CallbackSynchronizationSupport} for a given {@code latchCount} of callbacks
*/
protected CallbackSynchronizationSupport(int latchCount) {
latch = new CountDownLatch(latchCount);
}
/**
* Await results without a timeout.
*
* @throws InterruptedException
*/
public final void await() throws InterruptedException {
latch.await();
}
/**
* Await the results with a timeout.
*
* @param timeout must be greater or equal to 0
* @param timeUnit must not be {@literal null}.
* @throws InterruptedException
*/
public final void await(long timeout, TimeUnit timeUnit) throws InterruptedException {
Assert.isTrue(timeout >= 0, "Timeout must be greater or equal to 0");
Assert.notNull(timeUnit, "TimeUnit must not be null");
latch.await(timeout, timeUnit);
}
/**
* Indicate an incoming event and count down the latch by {@literal 1}.
*/
protected final void countDown() {
latch.countDown();
}
}

View File

@@ -13,31 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.core.async;
package org.springframework.cassandra.test.integration.support;
import java.util.List;
import org.springframework.cassandra.core.QueryForListListener;
import org.springframework.cassandra.support.TestListener;
/**
* {@link QueryForListListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class ListListener<T> extends TestListener implements QueryForListListener<T> {
public class ListListener<T> extends CallbackSynchronizationSupport implements QueryForListListener<T> {
Exception exception;
List<T> result;
private volatile Exception exception;
private volatile List<T> result;
/**
* Allow instances only using {@link #create()}
*/
private ListListener() {
super();
}
/**
* @return a new {@link QueryForListListener}.
*/
public static <T> ListListener<T> create() {
return new ListListener<T>();
}
@Override
public void onQueryComplete(List<T> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Exception getException() {
return exception;
}
public List<T> getResult() {
return result;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.support;
import java.util.List;
import java.util.Map;
import org.springframework.cassandra.core.QueryForListListener;
import org.springframework.cassandra.core.QueryForListOfMapListener;
/**
* {@link QueryForListListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class ListOfMapListener extends CallbackSynchronizationSupport implements QueryForListOfMapListener {
private volatile Exception exception;
private volatile List<Map<String, Object>> result;
/**
* Allow instances only using {@link #create()}
*/
private ListOfMapListener() {
super();
}
/**
* @return a new {@link QueryForListListener}.
*/
public static ListOfMapListener create() {
return new ListOfMapListener();
}
@Override
public void onQueryComplete(List<Map<String, Object>> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Exception getException() {
return exception;
}
public List<Map<String, Object>> getResult() {
return result;
}
}

View File

@@ -13,30 +13,56 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.core.async;
package org.springframework.cassandra.test.integration.support;
import java.util.Map;
import org.springframework.cassandra.core.QueryForMapListener;
import org.springframework.cassandra.support.TestListener;
/**
* {@link QueryForMapListener} suitable for tests.
*
* @author Matthew T. Adams
* @author Mark Paluch
*/
public class MapListener extends TestListener implements QueryForMapListener {
public class MapListener extends CallbackSynchronizationSupport implements QueryForMapListener {
Map<String, Object> result;
Exception exception;
private volatile Map<String, Object> result;
private volatile Exception exception;
/**
* Allow instances only using {@link #create()}
*/
private MapListener(){
super();
}
/**
* @return a new {@link ObjectListener}.
*/
public static MapListener create() {
return new MapListener();
}
@Override
public void onQueryComplete(Map<String, Object> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Map<String, Object> getResult() {
return result;
}
public Exception getException() {
return exception;
}
}

View File

@@ -13,19 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.core.async;
package org.springframework.cassandra.test.integration.support;
import org.springframework.cassandra.core.QueryForObjectListener;
import org.springframework.cassandra.support.TestListener;
/**
* {@link QueryForObjectListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class ObjectListener<T> extends TestListener implements QueryForObjectListener<T> {
public class ObjectListener<T> extends CallbackSynchronizationSupport implements QueryForObjectListener<T> {
public volatile T result;
public volatile Exception exception;
private volatile T result;
private volatile Exception exception;
/**
* Allow instances only using {@link #create()}
*/
private ObjectListener(){
super();
}
/**
* @return a new {@link ObjectListener}.
*/
public static <T> ObjectListener<T> create() {
return new ObjectListener<T>();
}
@Override
public void onQueryComplete(T result) {
@@ -40,4 +56,12 @@ public class ObjectListener<T> extends TestListener implements QueryForObjectLis
this.exception = x;
countDown();
}
public T getResult() {
return result;
}
public Exception getException() {
return exception;
}
}

View File

@@ -13,24 +13,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.test.integration.core.async;
package org.springframework.cassandra.test.integration.support;
import org.springframework.cassandra.core.AsynchronousQueryListener;
import org.springframework.cassandra.support.TestListener;
import com.datastax.driver.core.ResultSetFuture;
/**
* {@link AsynchronousQueryListener} suitable for usage in tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
class BasicListener extends TestListener implements AsynchronousQueryListener {
public class QueryListener extends CallbackSynchronizationSupport implements AsynchronousQueryListener {
ResultSetFuture rsf;
private volatile ResultSetFuture rsf;
/**
* Allow instances only using {@link #create()}
*/
private QueryListener() {
super();
}
/**
* @return a new {@link QueryListener}.
*/
public static QueryListener create() {
return new QueryListener();
}
@Override
public void onQueryComplete(ResultSetFuture rsf) {
this.rsf = rsf;
public void onQueryComplete(ResultSetFuture resultSetFuture) {
this.rsf = resultSetFuture;
countDown();
}
public ResultSetFuture getResultSetFuture() {
return rsf;
}
}