blob: 362dfc77a5ecea20c2cdf94234e49d6b08fa453a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.test.junit.rules;
import static org.apache.geode.util.internal.UncheckedUtils.uncheckedCast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.geode.test.junit.rules.serializable.SerializableExternalResource;
/**
* CloseableReference is a JUnit Rule that provides automated tearDown for an atomic reference. If
* the referenced value is an {@code AutoCloseable} or {@code Closeable} then it will be auto-closed
* and set to null during tear down.
*
* <p>
* If the referenced value is not an {@code AutoCloseable} or {@code Closeable}, the
* {@code CloseableReference} will use reflection to invoke any method named {@code close},
* {@code disconnect}, or {@code stop} regardless of what interfaces are implemented unless
* {@code autoClose} is set to false.
*
* <p>
* If the referenced value is null then it will be ignored during tear down.
*
* <p>
* In the following example, the test has a {@code ServerLauncher} which will be
* auto-stopped and set to null during tear down:
*
* <pre>
* {@literal @}Rule
* public CloseableReference&lt;ServerLauncher&gt; server = new CloseableReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() throws IOException {
* server.set(new ServerLauncher.Builder()
* .setMemberName("server1")
* .setDisableDefaultServer(true)
* .reinitializeWithWorkingDirectory(temporaryFolder.newFolder("server1").getAbsolutePath())
* .build());
*
* server.get().start();
* }
*
* {@literal @}Test
* public void serverHasCache() {
* assertThat(server.get().getCache()).isNotNull();
* }
* </pre>
*
* <p>
* In the following example, the test has a {@code Cache} which will be auto-closed and set to null
* during tear down:
*
* <pre>
* {@literal @}Rule
* public CloseableReference&lt;Cache&gt; cache = new CloseableReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() {
* cache.set(new CacheFactory().create());
* }
*
* {@literal @}Test
* public void cacheExists() {
* assertThat(cache.get()).isNotNull();
* }
* </pre>
*
* <p>
* In the following example, the test has a {@code DistributedSystem} which will be
* auto-disconnected and set to null during tear down:
*
* <pre>
* {@literal @}Rule
* public CloseableReference&lt;DistributedSystem&gt; system = new CloseableReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() {
* system.set(DistributedSystem.connect());
* }
*
* {@literal @}Test
* public void distributedSystemExists() {
* assertThat(system.get()).isNotNull();
* }
* </pre>
*
* <p>
* To disable auto-closing in a test, specify {@code autoClose(false)}:
*
* <pre>
* {@literal @}Rule
* public CloseableReference&lt;ServerLauncher&gt; serverLauncher =
* new CloseableReference&lt;&gt;().autoClose(false);
* </pre>
*
* <p>
* The {@code CloseableReference} value will still be set to null during tear down even if
* auto-closing is disabled.
*/
@SuppressWarnings({"serial", "WeakerAccess"})
public class CloseableReference<V> extends SerializableExternalResource {
private static final AtomicReference<Object> reference = new AtomicReference<>();
private final AtomicBoolean autoClose = new AtomicBoolean(true);
/*
* Set false to disable autoClose during tearDown. Default is true.
*/
public CloseableReference<V> autoClose(boolean value) {
autoClose.set(value);
return this;
}
/**
* Gets the current value.
*
* @return the current value
*/
public V get() {
return uncheckedCast(reference.get());
}
/**
* Sets to the given value.
*
* @param newValue the new value
* @return a reference to this CloseableReference
*/
public CloseableReference<V> set(V newValue) {
reference.set(newValue);
return this;
}
@Override
protected void after() {
V value = get();
if (value == null) {
return;
}
reference.set(null);
if (autoClose.get()) {
autoClose(value);
}
}
private void autoClose(V value) {
if (value instanceof AutoCloseable) {
close((AutoCloseable) value);
} else if (hasMethod(value.getClass(), "close")) {
invokeMethod(value, "close");
} else if (hasMethod(value.getClass(), "disconnect")) {
invokeMethod(value, "disconnect");
} else if (hasMethod(value.getClass(), "stop")) {
invokeMethod(value, "stop");
}
}
private static void close(AutoCloseable autoCloseable) {
try {
autoCloseable.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static boolean hasMethod(Class<?> objectClass, String methodName) {
try {
Method method = objectClass.getMethod(methodName);
// currently only supports public method with zero parameters
if (method.getParameterCount() == 0 &&
Modifier.isPublic(method.getModifiers())) {
return true;
}
} catch (NoSuchMethodException e) {
// ignore
}
return false;
}
private static void invokeMethod(Object object, String methodName) {
try {
Method method = object.getClass().getMethod(methodName);
method.invoke(object);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}