| /* |
| * 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<ServerLauncher> server = new CloseableReference<>(); |
| * |
| * {@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<Cache> cache = new CloseableReference<>(); |
| * |
| * {@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<DistributedSystem> system = new CloseableReference<>(); |
| * |
| * {@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<ServerLauncher> serverLauncher = |
| * new CloseableReference<>().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); |
| } |
| } |
| } |