blob: 9e152931698d4481fb3fa75985b196b2623c89e1 [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.dunit.rules;
import static org.apache.geode.test.dunit.VM.DEFAULT_VM_COUNT;
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;
/**
* DistributedReference is a JUnit Rule that provides automated tearDown for a static
* reference in every distributed test {@code VM}s including the main JUnit controller {@code VM}.
* If the referenced value is an {@code AutoCloseable} or {@code Closeable} then it will be
* auto-closed and set to null in every {@code VM} during tear down.
*
* <p>
* If the referenced value is not an {@code AutoCloseable} or {@code Closeable}, the
* {@code DistributedReference} 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 in any {@code VM} then it will be ignored in that {@code VM}
* during tear down.
*
* <p>
* In the following example, every {@code VM} has a {@code ServerLauncher} which will be
* auto-stopped and set to null during tear down:
*
* <pre>
* {@literal @}Rule
* public DistributedReference&lt;ServerLauncher&gt; server = new DistributedReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() throws IOException {
* Properties configProperties = new Properties();
* configProperties.setProperty(LOCATORS, DistributedRule.getLocators());
*
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* server.set(new ServerLauncher.Builder()
* .setMemberName("server" + getVMId())
* .setDisableDefaultServer(true)
* .setWorkingDirectory(temporaryFolder.newFolder("server" + getVMId()).getAbsolutePath())
* .build());
*
* server.get().start();
* });
* }
* }
*
* {@literal @}Test
* public void eachVmHasItsOwnServerCache() {
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* assertThat(server.get().getCache()).isNotNull();
* });
* }
* </pre>
*
* <p>
* In the following example, every {@code VM} has a {@code Cache} which will be auto-closed and set
* to null during tear down:
*
* <pre>
* {@literal @}Rule
* public DistributedReference&lt;Cache&gt; cache = new DistributedReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() {
* Properties configProperties = new Properties();
* configProperties.setProperty(LOCATORS, DistributedRule.getLocators());
*
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* cache.set(new CacheFactory(configProperties).create());
* });
* }
* }
*
* {@literal @}Test
* public void eachVmHasItsOwnCache() {
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* assertThat(cache.get()).isNotNull();
* });
* }
* }
* </pre>
*
* <p>
* In the following example, every {@code VM} has a {@code DistributedSystem} which will be
* auto-disconnected and set to null during tear down:
*
* <pre>
* {@literal @}Rule
* public DistributedReference&lt;DistributedSystem&gt; system = new DistributedReference&lt;&gt;();
*
* {@literal @}Before
* public void setUp() {
* Properties configProperties = new Properties();
* configProperties.setProperty(LOCATORS, DistributedRule.getLocators());
*
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* system.set(DistributedSystem.connect(configProperties));
* });
* }
* }
*
* {@literal @}Test
* public void eachVmHasItsOwnDistributedSystemConnection() {
* for (VM vm : toArray(getVM(0), getVM(1), getVM(2), getVM(3), getController())) {
* vm.invoke(() -> {
* assertThat(system.get()).isNotNull();
* });
* }
* }
* </pre>
*
* <p>
* To disable auto-closing in a test, specify {@code autoClose(false)}:
*
* <pre>
* {@literal @}Rule
* public DistributedReference&lt;ServerLauncher&gt; serverLauncher =
* new DistributedReference&lt;&gt;().autoClose(false);
* </pre>
*
* <p>
* The {@code DistributedReference} value will still be set to null during tear down even
* if auto-closing is disabled.
*/
@SuppressWarnings({"serial", "unused", "WeakerAccess"})
public class DistributedReference<V> extends AbstractDistributedRule {
private static final AtomicReference<Object> REFERENCE = new AtomicReference<>();
private final AtomicBoolean autoClose = new AtomicBoolean(true);
public DistributedReference() {
this(DEFAULT_VM_COUNT);
}
public DistributedReference(int vmCount) {
super(vmCount);
}
/**
* Set false to disable autoClose during tearDown. Default is true.
*/
public DistributedReference<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
*/
public DistributedReference<V> set(V newValue) {
REFERENCE.set(newValue);
return this;
}
@Override
protected void after() {
invoker().invokeInEveryVMAndController(this::invokeAfter);
}
private void invokeAfter() {
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);
Class<?> returnType = method.getReturnType();
// 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);
}
}
}