Rename spring-test-data-gemfire module to spring-data-gemfire-test.

Rename spring-test-data-geode module to spring-data-geode-test.
This commit is contained in:
John Blum
2018-06-01 18:20:33 -07:00
parent 827383e5f2
commit 1195c89ad0
41 changed files with 10 additions and 13 deletions

View File

@@ -0,0 +1,261 @@
/*
* Copyright 2018 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.data.gemfire.tests.integration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.gemfire.tests.process.ProcessExecutor.launch;
import static org.springframework.data.gemfire.util.ArrayUtils.asArray;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.geode.cache.server.CacheServer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.gemfire.tests.process.ProcessWrapper;
import org.springframework.data.gemfire.tests.util.FileSystemUtils;
import org.springframework.data.gemfire.tests.util.FileUtils;
import org.springframework.data.gemfire.tests.util.SocketUtils;
import org.springframework.data.gemfire.tests.util.ThreadUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
/**
* The {@link ClientServerIntegrationTestsSupport} class is a abstract base class encapsulating common functionality
* to support the implementation of GemFire client/server tests.
*
* @author John Blum
* @see java.io.File
* @see java.net.ServerSocket
* @see java.net.Socket
* @see java.time.LocalDateTime
* @see org.apache.geode.cache.server.CacheServer
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.process.ProcessExecutor
* @see org.springframework.data.gemfire.tests.process.ProcessWrapper
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ClientServerIntegrationTestsSupport extends IntegrationTestsSupport {
public static final String DEFAULT_HOSTNAME = "localhost";
public static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY = "spring.data.gemfire.cache.server.port";
protected static final String DEBUG_ENDPOINT = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005";
protected static final String DEBUGGING_ENABLED_PROPERTY = "spring.data.gemfire.debugging.enabled";
protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "spring.data.gemfire.directory.delete-on-exit";
protected static final String PROCESS_RUN_MANUAL_PROPERTY = "spring.data.gemfire.process.run-manual";
protected static final String SYSTEM_PROPERTIES_LOG_FILE = "system-properties.log";
protected static String asApplicationName(Class<?> type) {
return type.getSimpleName();
}
protected static String asDirectoryName(Class<?> type) {
return String.format("%1$s-%2$s", asApplicationName(type),
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-hh-mm-ss")));
}
protected static File createDirectory(String pathname) {
return createDirectory(new File(pathname));
}
protected static File createDirectory(File directory) {
assertThat(directory.isDirectory() || directory.mkdirs())
.as(String.format("Failed to create directory [%s]", directory)).isTrue();
if (isDeleteDirectoryOnExit()) {
directory.deleteOnExit();
}
return directory;
}
protected static int findAvailablePort() throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(0);
return serverSocket.getLocalPort();
}
finally {
SocketUtils.close(serverSocket);
}
}
protected static String getClassNameAsPath(Class type) {
return type.getName().replaceAll("\\.", "/");
}
protected static String getClassNameAsPath(Object obj) {
return getClassNameAsPath(obj.getClass());
}
protected static String getPackageNameAsPath(Class type) {
return type.getPackage().getName().replaceAll("\\.", "/");
}
protected static String getPackageNameAsPath(Object obj) {
return getPackageNameAsPath(obj.getClass());
}
protected static String getContextXmlFileLocation(Class type) {
return getClassNameAsPath(type).concat("-context.xml");
}
protected static String getServerContextXmlFileLocation(Class type) {
return getClassNameAsPath(type).concat("-server-context.xml");
}
protected static boolean isDeleteDirectoryOnExit() {
return Boolean.valueOf(System.getProperty(DIRECTORY_DELETE_ON_EXIT_PROPERTY, Boolean.TRUE.toString()));
}
protected static int intValue(Number number) {
return number != null ? number.intValue() : 0;
}
protected static String logFile() {
return logFile(GEMFIRE_LOG_FILE);
}
protected static String logFile(String defaultLogFilePathname) {
return System.getProperty(GEMFIRE_LOG_FILE_PROPERTY, defaultLogFilePathname);
}
protected static String logLevel() {
return logLevel(GEMFIRE_LOG_LEVEL);
}
protected static String logLevel(String defaultLogLevel) {
return System.getProperty(GEMFIRE_LOG_LEVEL_PROPERTY, defaultLogLevel);
}
protected static void logSystemProperties() throws IOException {
FileUtils.write(new File(SYSTEM_PROPERTIES_LOG_FILE),
String.format("%s", CollectionUtils.toString(System.getProperties())));
}
protected static ProcessWrapper run(Class<?> type, String... arguments) throws IOException {
return run(createDirectory(asDirectoryName(type)), type, arguments);
}
protected static ProcessWrapper run(File workingDirectory, Class<?> type, String... arguments) throws IOException {
return isProcessRunAuto() ? launch(createDirectory(workingDirectory), type, arguments) : null;
}
protected static ProcessWrapper run(String classpath, Class<?> type, String... arguments) throws IOException {
return run(createDirectory(asDirectoryName(type)), classpath, type, arguments);
}
protected static ProcessWrapper run(File workingDirectory, String classpath, Class<?> type, String... arguments)
throws IOException {
return isProcessRunAuto() ? launch(createDirectory(workingDirectory), classpath, type, arguments) : null;
}
protected static boolean isProcessRunAuto() {
return !isProcessRunManual();
}
protected static boolean isProcessRunManual() {
return Boolean.getBoolean(PROCESS_RUN_MANUAL_PROPERTY);
}
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?> annotatedClass, String... args) {
return runSpringApplication(asArray(annotatedClass), args);
}
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?>[] annotatedClasses,
String... args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(annotatedClasses);
applicationContext.registerShutdownHook();
return applicationContext;
}
protected static boolean stop(ProcessWrapper process) {
return stop(process, DEFAULT_WAIT_DURATION);
}
protected static boolean stop(ProcessWrapper process, long duration) {
return Optional.ofNullable(process)
.map(it -> {
it.stop(duration);
if (it.isNotRunning() && isDeleteDirectoryOnExit()) {
FileSystemUtils.deleteRecursive(it.getWorkingDirectory());
}
return it.isRunning();
})
.orElse(true);
}
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION);
}
protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) {
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration);
}
protected static boolean waitForServerToStart(String host, int port) {
return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION);
}
protected static boolean waitForServerToStart(final String host, final int port, long duration) {
return ThreadUtils.timedWait(duration, DEFAULT_WAIT_INTERVAL, new ThreadUtils.WaitCondition() {
AtomicBoolean connected = new AtomicBoolean(false);
public boolean waiting() {
Socket socket = null;
try {
if (!connected.get()) {
socket = new Socket(host, port);
connected.set(true);
}
}
catch (IOException ignore) {
}
finally {
SocketUtils.close(socket);
}
return !connected.get();
}
});
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2018 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.data.gemfire.tests.integration;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.junit.AfterClass;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
import org.springframework.data.gemfire.tests.integration.config.ClientServerIntegrationTestsConfiguration;
import org.springframework.data.gemfire.tests.process.ProcessWrapper;
/**
* The {@link ForkingClientServerIntegrationTestsSupport} class...
*
* @author John Blum
* @see org.springframework.data.gemfire.config.annotation.CacheServerApplication
* @see org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.process.ProcessWrapper
* @since 1.0.0
*/
@SuppressWarnings("unused")
// TODO: this class is a WIP; I need to figure out client/server configuration and logistics
// when launching a CacheServer; this class will be replaced by a JUnit Rule anyhow
public abstract class ForkingClientServerIntegrationTestsSupport extends ClientServerIntegrationTestsSupport {
private static ProcessWrapper gemfireServer;
public static void startGemFireServer(Class<?> gemfireServerConfigurationClass, String... arguments)
throws IOException {
int availablePort = setAndGetCacheServerPortProperty();
List<String> argumentList = new ArrayList<>();
argumentList.addAll(Arrays.asList(nullSafeArray(arguments, String.class)));
argumentList.add(String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort));
setGemFireServerProcess(run(gemfireServerConfigurationClass,
argumentList.toArray(new String[argumentList.size()])));
waitForServerToStart("localhost", availablePort);
}
protected static int setAndGetCacheServerPortProperty() throws IOException {
int availablePort = findAvailablePort();
System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort));
return availablePort;
}
@AfterClass
public static void stopGemFireServer() {
getGemFireServerProcess().ifPresent(ForkingClientServerIntegrationTestsSupport::stop);
setGemFireServerProcess(null);
}
@AfterClass
public static void clearCacheServerPortProperty() {
System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY);
}
protected static synchronized Optional<ProcessWrapper> getGemFireServerProcess() {
return Optional.ofNullable(gemfireServer);
}
protected static synchronized void setGemFireServerProcess(ProcessWrapper gemfireServerProcess) {
gemfireServer = gemfireServerProcess;
}
@EnablePdx
@ClientCacheApplication(logLevel = GEMFIRE_LOG_FILE)
protected static class BaseGemFireClientConfiguration extends ClientServerIntegrationTestsConfiguration { }
@EnablePdx
@CacheServerApplication(name = "ForkingClientServerIntegrationTestsSupport", logLevel = GEMFIRE_LOG_LEVEL)
public static class BaseGemFireServerConfiguration extends ClientServerIntegrationTestsConfiguration {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(BaseGemFireServerConfiguration.class);
applicationContext.registerShutdownHook();
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2018 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.data.gemfire.tests.integration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.geode.cache.CacheClosedException;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.internal.net.SocketCreatorFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport;
/**
* The {@link IntegrationTestsSupport} class is an abstract base class supporting integration tests
* with either Apache Geode or Pivotal GemFire in a Spring context.
*
* @author John Blum
* @see org.apache.geode.cache.GemFireCache
* @since 1.0.0
*/
@SuppressWarnings("unused")
public abstract class IntegrationTestsSupport {
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(30);
protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds
protected static final String GEMFIRE_LOG_FILE = "gemfire-server.log";
protected static final String GEMFIRE_LOG_FILE_PROPERTY = "spring.data.gemfire.log.file";
protected static final String GEMFIRE_LOG_LEVEL = "error";
protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.log.level";
protected static final String TEST_GEMFIRE_LOG_LEVEL = "error";
private static final Predicate<String> GEMFIRE_DOT_SYSTEM_PROPERTY_NAME_PREDICATE =
propertyName -> String.valueOf(propertyName).toLowerCase().startsWith("gemfire");
private static final Predicate<String> GEODE_DOT_SYSTEM_PROPERTY_NAME_PREDICATE =
propertyName -> String.valueOf(propertyName).toLowerCase().startsWith("geode");
private static final Predicate<String> SPRING_DOT_SYSTEM_PROPERTY_NAME_PREDICATE =
propertyName -> String.valueOf(propertyName).toLowerCase().startsWith("spring");
private static final Predicate<String> ALL_SYSTEM_PROPERTIES_NAME_PREDICATE =
GEMFIRE_DOT_SYSTEM_PROPERTY_NAME_PREDICATE
.or(GEODE_DOT_SYSTEM_PROPERTY_NAME_PREDICATE)
.or(SPRING_DOT_SYSTEM_PROPERTY_NAME_PREDICATE);
@BeforeClass
public static void clearAllSpringGeodeGemFireDotPrefixedSystemProperties() {
List<String> springSystemProperties = System.getProperties().stringPropertyNames().stream()
.filter(ALL_SYSTEM_PROPERTIES_NAME_PREDICATE)
.collect(Collectors.toList());
springSystemProperties.forEach(System::clearProperty);
}
@BeforeClass
public static void closeAnyExistingGemFireCacheInstanceBeforeTestExecution() {
closeGemFireCacheWaitOnCloseEvent();
}
@BeforeClass
public static void closeAnyExistingSocketConfigurationBeforeTestExecution() {
SocketCreatorFactory.close();
//SSLConfigurationFactory.close();
}
@AfterClass
public static void destroyAllGemFireMockObjects() {
GemFireMockObjectsSupport.destroy();
}
public static void closeGemFireCacheWaitOnCloseEvent() {
closeGemFireCacheWaitOnCloseEvent(DEFAULT_WAIT_DURATION);
}
public static void closeGemFireCacheWaitOnCloseEvent(long duration) {
AtomicBoolean closed = new AtomicBoolean(false);
waitOn(() -> {
try {
return Optional.ofNullable(GemfireUtils.resolveGemFireCache())
.filter(cache -> !closed.get())
.filter(cache -> !cache.isClosed())
.map(IntegrationTestsSupport::close)
.map(GemFireCache::isClosed)
.orElse(true);
}
catch (CacheClosedException ignore) {
closed.set(true);
return true;
}
}, duration);
}
private static GemFireCache close(GemFireCache cache) {
return Optional.ofNullable(cache)
.map(it -> {
cache.close();
return cache;
}).orElse(cache);
}
protected static boolean waitOn(Condition condition) {
return waitOn(condition, DEFAULT_WAIT_DURATION);
}
@SuppressWarnings("all")
protected static boolean waitOn(Condition condition, long duration) {
long timeout = System.currentTimeMillis() + duration;
try {
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
synchronized (condition) {
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
}
}
}
catch (InterruptedException cause) {
Thread.currentThread().interrupt();
}
return condition.evaluate();
}
@FunctionalInterface
protected interface Condition {
boolean evaluate();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2018 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.data.gemfire.tests.integration.config;
import static org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport.GEMFIRE_CACHE_SERVER_PORT_PROPERTY;
import java.util.Collections;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.Pool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.config.annotation.CacheServerConfigurer;
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
/**
* The {@link ClientServerIntegrationTestsConfiguration} class is a Spring {@link Configuration} class
* that registers a {@link ClientCacheConfigurer} used to configure the {@link ClientCache} {@link Pool} port
* to connect to the launched Apache Geode/Pivotal GemFire Server during integration testing.
*
* @author John Blum
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.Pool
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
* @since 1.0.0
*/
@Configuration
@SuppressWarnings("unused")
public class ClientServerIntegrationTestsConfiguration {
private final Logger logger = LoggerFactory.getLogger(getClass());
protected Logger getLogger() {
return this.logger;
}
@Bean
ClientCacheConfigurer clientCachePoolPortConfigurer(
@Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) {
return (beanName, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers(
Collections.singletonList(new ConnectionEndpoint("localhost", port)));
}
@Bean
CacheServerConfigurer cacheServerPortConfigurer(
@Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) {
return (beanName, cacheServerFactoryBean) -> cacheServerFactoryBean.setPort(port);
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright 2018 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.data.gemfire.tests.integration.config;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.cache.client.internal.PoolImpl;
import org.apache.geode.management.membership.ClientMembership;
import org.apache.geode.management.membership.ClientMembershipEvent;
import org.apache.geode.management.membership.ClientMembershipListenerAdapter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionShortcutWrapper;
import org.springframework.data.gemfire.client.PoolFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.listener.ContinuousQueryListenerContainer;
import org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport;
import org.springframework.data.gemfire.tests.util.ObjectUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* The {@link SubscriptionEnabledClientServerIntegrationTestsConfiguration} class is a base Spring {@link Configuration}
* class supporting Apache Geode or Pivotal GemFire client/server integration tests when subscriptions are enabled.
*
* Subscriptions must be enabled when {@literal Registering Interests} or {@literal Continuous Queries (CQ)}.
*
* @author John Blum
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.Pool
* @see org.apache.geode.cache.client.PoolManager
* @see org.apache.geode.cache.client.internal.PoolImpl
* @see org.apache.geode.management.membership.ClientMembership
* @see org.apache.geode.management.membership.ClientMembershipListenerAdapter
* @see org.springframework.beans.factory.ListableBeanFactory
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.data.gemfire.client.ClientCacheFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.data.gemfire.client.PoolFactoryBean
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
* @see org.springframework.data.gemfire.listener.ContinuousQueryListenerContainer
* @see org.springframework.data.gemfire.tests.integration.ClientServerIntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.integration.config.ClientServerIntegrationTestsConfiguration
* @since 1.0.0
*/
@Configuration
@SuppressWarnings("unused")
public class SubscriptionEnabledClientServerIntegrationTestsConfiguration
extends ClientServerIntegrationTestsConfiguration {
private static final boolean DEFAULT_SUBSCRIPTION_QUEUE_CONNECTION_FAILURE = true;
private static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private static final CountDownLatch LATCH = new CountDownLatch(1);
private static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY =
ClientServerIntegrationTestsSupport.GEMFIRE_CACHE_SERVER_PORT_PROPERTY;
private static final String SPRING_DATA_GEODE_POOL_NAME = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
private static final String GEMFIRE_DEFAULT_POOL_NAME = "DEFAULT";
private static final String LOCALHOST = ClientServerIntegrationTestsSupport.DEFAULT_HOSTNAME;
protected boolean isThrowExceptionOnSubscriptionQueueConnectionFailure() {
return DEFAULT_SUBSCRIPTION_QUEUE_CONNECTION_FAILURE;
}
@Bean
BeanPostProcessor clientServerReadyBeanPostProcessor(ListableBeanFactory beanFactory,
@Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) {
return new BeanPostProcessor() {
private final AtomicBoolean verifyGemFireServerIsRunning = new AtomicBoolean(true);
@Nullable
@Override
@SuppressWarnings("all")
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (shouldVerifyGemFireServerIsRunning(bean, beanName)) {
try {
verifyClientCacheSubscriptionQueueConnectionsEstablished();
verifyClientCacheNotified();
}
catch (InterruptedException cause) {
Thread.currentThread().interrupt();
}
}
return bean;
}
private boolean shouldVerifyGemFireServerIsRunning(Object bean, String beanName) {
return isBeanOfImportance(bean, beanName)
&& verifyGemFireServerIsRunning.compareAndSet(true, false);
}
private boolean isBeanOfImportance(Object bean, String beanName) {
return isContinuousQueryListenerContainer(bean) || isProxyClientRegion(bean);
}
private boolean isClientCache(Object bean) {
return bean instanceof ClientCacheFactoryBean;
}
private boolean isContinuousQueryListenerContainer(Object bean) {
return bean instanceof ContinuousQueryListenerContainer;
}
private boolean isProxyClientRegion(Object bean) {
if (bean instanceof ClientRegionFactoryBean) {
ClientRegionFactoryBean<?, ?> clientRegionFactoryBean = (ClientRegionFactoryBean) bean;
Optional<String> poolName = clientRegionFactoryBean.getPoolName()
.filter(StringUtils::hasText);
Optional<ClientRegionShortcut> clientRegionShortcut =
resolveClientRegionShortcut(clientRegionFactoryBean)
.filter(this::isProxyClientRegion);
return poolName.isPresent() || clientRegionShortcut.isPresent();
}
return false;
}
private boolean isProxyClientRegion(ClientRegionShortcut clientRegionShortcut) {
return ClientRegionShortcutWrapper.valueOf(clientRegionShortcut).isProxy();
}
@SuppressWarnings("unchecked")
private Optional<ClientRegionShortcut> resolveClientRegionShortcut(
ClientRegionFactoryBean<?, ?> clientRegionFactoryBean) {
try {
Method resolveClientRegionShortcut = ClientRegionFactoryBean.class
.getDeclaredMethod("resolveClientRegionShortcut");
resolveClientRegionShortcut.setAccessible(true);
return Optional.ofNullable((ClientRegionShortcut)
ReflectionUtils.invokeMethod(resolveClientRegionShortcut, clientRegionFactoryBean));
}
catch (Throwable ignore) {
return Optional.empty();
}
}
@SuppressWarnings("all")
private void verifyClientCacheNotified() throws InterruptedException {
boolean success = LATCH.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
String errorMessage = String.format("CacheServer failed to start on host [%s] and port [%d]",
LOCALHOST, port);
if (success) {
Assert.state(success, errorMessage);
}
else if (getLogger().isWarnEnabled()) {
getLogger().warn(errorMessage);
}
}
@SuppressWarnings("all")
private void verifyClientCacheSubscriptionQueueConnectionsEstablished() {
resolvePools().stream()
.filter(pool -> pool instanceof PoolImpl)
.map(pool -> (PoolImpl) pool)
.forEach(pool -> {
long timeout = System.currentTimeMillis() + DEFAULT_TIMEOUT;
while (System.currentTimeMillis() < timeout && !pool.isPrimaryUpdaterAlive()) {
synchronized (pool) {
ObjectUtils.doOperationSafely(() -> {
TimeUnit.MILLISECONDS.timedWait(pool, 500L);
return null;
});
}
}
String errorMessage = String.format("ClientCache subscription queue connection not established;"
+ " Pool [%s] has configuration [locators = %s, servers = %s]",
pool, pool.getLocators(), pool.getServers());
if (isThrowExceptionOnSubscriptionQueueConnectionFailure()) {
Assert.state(pool.isPrimaryUpdaterAlive(), errorMessage);
}
else if (getLogger().isWarnEnabled()){
getLogger().warn(errorMessage);
}
});
}
private Collection<Pool> resolvePools() {
eagerlyInitializeSpringManagedPoolBeans();
return nullSafeMap(PoolManager.getAll()).values();
}
private void eagerlyInitializeSpringManagedPoolBeans() {
beanFactory.getBeansOfType(PoolFactoryBean.class).keySet()
.forEach(beanName -> beanFactory.getBean(beanName, Pool.class));
}
};
}
@Bean
ClientCacheConfigurer registerClientMembershipListener() {
return (beanName, bean) ->
ClientMembership.registerClientMembershipListener(new ClientMembershipListenerAdapter() {
@Override
public void memberJoined(ClientMembershipEvent event) {
LATCH.countDown();
}
});
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.util.StringUtils;
/**
* The {@link MockObjectsSupport} class is an abstract base class encapsulating common operations and utilities
* used in mocking using Mockito.
*
* @author John Blum
* @see org.mockito.invocation.InvocationOnMock
* @see org.mockito.stubbing.Answer
* @since 0.0.1
*/
@SuppressWarnings("all")
public abstract class MockObjectsSupport {
private static final AtomicLong mockObjectIdentifier = new AtomicLong(0L);
private static final String DEFAULT_MOCK_OBJECT_NAME = "MockObject";
public static String mockObjectIdentifier() {
return mockObjectIdentifier(DEFAULT_MOCK_OBJECT_NAME);
}
public static String mockObjectIdentifier(String mockObjectName) {
String resolvedMockObjectName = Optional.ofNullable(mockObjectName)
.filter(StringUtils::hasText)
.orElse(DEFAULT_MOCK_OBJECT_NAME);
return String.format("%s%d", resolvedMockObjectName, mockObjectIdentifier.incrementAndGet());
}
protected static Answer<Boolean> newGetter(AtomicBoolean returnValue) {
return invocation -> returnValue.get();
}
protected static Answer<Integer> newGetter(AtomicInteger returnValue) {
return invocation -> returnValue.get();
}
protected static Answer<Long> newGetter(AtomicLong returnValue) {
return invocation -> returnValue.get();
}
protected static <R> Answer<R> newGetter(AtomicReference<R> returnValue) {
return invocation -> returnValue.get();
}
protected static <R, S> Answer<S> newGetter(AtomicReference<R> returnValue, Function<R, S> converter) {
return invocation -> converter.apply(returnValue.get());
}
protected static <R> Answer<R> newGetter(Supplier<R> returnValue) {
return invocation -> returnValue.get();
}
protected static <R, S> Answer<S> newGetter(Supplier<R> returnValue, Function<R, S> converter) {
return invocation -> converter.apply(returnValue.get());
}
protected static <E, C extends Collection<E>, R> Answer<R> newAdder(C collection, R returnValue) {
return invocation -> {
collection.add(invocation.getArgument(0));
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicBoolean argument, R returnValue) {
return invocation -> {
argument.set(invocation.getArgument(0));
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicBoolean argument, Boolean value, R returnValue) {
return invocation -> {
argument.set(value);
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicInteger argument, R returnValue) {
return invocation -> {
argument.set(invocation.getArgument(0));
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicInteger argument, Integer value, R returnValue) {
return invocation -> {
argument.set(value);
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicLong argument, R returnValue) {
return invocation -> {
argument.set(invocation.getArgument(0));
return returnValue;
};
}
protected static <R> Answer<R> newSetter(AtomicLong argument, Long value, R returnValue) {
return invocation -> {
argument.set(value);
return returnValue;
};
}
protected static <T, R> Answer<R> newSetter(AtomicReference<T> argument, R returnValue) {
return invocation -> {
argument.set(invocation.getArgument(0));
return returnValue;
};
}
protected static <T, R> Answer<R> newSetter(AtomicReference<T> argument, T value, R returnValue) {
return invocation -> {
argument.set(value);
return returnValue;
};
}
protected static <T, R> Answer<R> newSetter(AtomicReference<T> argument, Function<?, T> converter, R returnValue) {
return invocation -> {
argument.set(converter.apply(invocation.getArgument(0)));
return returnValue;
};
}
protected static <K, V, R> Answer<R> newSetter(Map<K, V> argument, R returnValue) {
return invocation -> {
argument.put(invocation.getArgument(0), invocation.getArgument(1));
return returnValue;
};
}
protected static <T> Answer<Void> newVoidAnswer(Consumer<InvocationOnMock> methodInvocation) {
return invocation -> {
methodInvocation.accept(invocation);
return null;
};
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apache.geode.cache.GemFireCache;
import org.springframework.context.annotation.Import;
/**
* The {@link EnableGemFireMockObjects} annotation enables mocking of GemFire Objects in Unit Tests.
*
* @author John Blum
* @see Documented
* @see Inherited
* @see Retention
* @see Target
* @see org.apache.geode.cache.GemFireCache
* @see org.springframework.context.annotation.Import
* @since 0.0.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(GemFireMockObjectsConfiguration.class)
@SuppressWarnings("unused")
public @interface EnableGemFireMockObjects {
/**
* Configures whether the mock {@link GemFireCache} created for Unit Testing is a Singleton.
*
* Defaults to {@literal false}.
*
* @return a boolean value indicating whether the mock {@link GemFireCache} created for Unit Testing
* is a Singleton.
*/
boolean useSingletonCache() default false;
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.annotation;
import java.lang.annotation.Annotation;
import java.util.Optional;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport;
import org.springframework.data.gemfire.tests.mock.config.GemFireMockObjectsBeanPostProcessor;
/**
* The {@link GemFireMockObjectsConfiguration} class is a Spring {@link Configuration @Configuration} class
* containing bean definitions to configure GemFire Object mocking.
*
* @author John Blum
* @see java.lang.annotation.Annotation
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.core.annotation.AnnotationAttributes
* @see org.springframework.core.type.AnnotationMetadata
* @see org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport
* @see org.springframework.data.gemfire.tests.mock.config.GemFireMockObjectsBeanPostProcessor
* @since 0.0.1
*/
@Configuration
@SuppressWarnings("unused")
public class GemFireMockObjectsConfiguration implements ImportAware {
private boolean useSingletonCache = false;
@Override
public void setImportMetadata(AnnotationMetadata importingClassMetadata) {
Optional.of(importingClassMetadata)
.filter(this::isAnnotationPresent)
.map(this::getAnnotationAttributes)
.ifPresent(enableGemFireMockObjectsAttributes ->
this.useSingletonCache = enableGemFireMockObjectsAttributes.getBoolean("useSingletonCache"));
}
private Class<? extends Annotation> getAnnotationType() {
return EnableGemFireMockObjects.class;
}
private boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata) {
return isAnnotationPresent(importingClassMetadata, getAnnotationType());
}
private boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata,
Class<? extends Annotation> annotationType) {
return importingClassMetadata.hasAnnotation(annotationType.getName());
}
private AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata) {
return getAnnotationAttributes(importingClassMetadata, getAnnotationType());
}
private AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata,
Class<? extends Annotation> annotationType) {
return AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(annotationType.getName()));
}
@Bean
public BeanPostProcessor gemfireMockObjectsBeanPostProcessor() {
return GemFireMockObjectsBeanPostProcessor.newInstance(this.useSingletonCache);
}
@EventListener
public void releaseMockResources(ContextClosedEvent event) {
GemFireMockObjectsSupport.destroy();
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.runner.RunWith;
import org.springframework.data.gemfire.tests.mock.context.GemFireMockObjectsApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* The {@link GemFireUnitTest} annotation marks a test class as a GemFire Unit Test
* with GemFire Object mocking enabled.
*
* Additionally, this annotation enables Spring's {@link SpringRunner} JUnit Runner implementation
* using JUnit's {@link RunWith} annotation.
*
* @author John Blum
* @see java.lang.annotation.Documented
* @see java.lang.annotation.Inherited
* @see java.lang.annotation.Retention
* @see java.lang.annotation.Target
* @see org.junit.runner.RunWith
* @see org.springframework.data.gemfire.tests.mock.context.GemFireMockObjectsApplicationContextInitializer
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @since 0.0.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@RunWith(SpringRunner.class)
@ContextConfiguration(initializers = GemFireMockObjectsApplicationContextInitializer.class)
@SuppressWarnings("unused")
public @interface GemFireUnitTest {
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.config;
import static org.mockito.Mockito.when;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.client.ClientCacheFactory;
import org.apache.geode.cache.client.PoolFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.client.PoolFactoryBean;
import org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport;
import org.springframework.lang.Nullable;
/**
* The {@link GemFireMockObjectsBeanPostProcessor} class is a Spring {@link BeanPostProcessor} that applies
* mocks and spies to Spring Data GemFire / Spring Data Geode and Pivotal GemFire / Apache Geode objects
* and components.
*
* @author John Blum
* @see org.apache.geode.cache.CacheFactory
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.client.ClientCacheFactory
* @see org.apache.geode.cache.client.PoolFactory
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.data.gemfire.CacheFactoryBean
* @see org.springframework.data.gemfire.client.ClientCacheFactoryBean
* @see org.springframework.data.gemfire.client.PoolFactoryBean
* @see org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport
* @since 0.0.1
*/
@SuppressWarnings("all")
public class GemFireMockObjectsBeanPostProcessor implements BeanPostProcessor {
private static final boolean DEFAULT_USE_SINGLETON_CACHE = false;
private static final String GEMFIRE_PROPERTIES_BEAN_NAME = "gemfireProperties";
private volatile boolean useSingletonCache;
private final AtomicReference<Properties> gemfireProperties = new AtomicReference<>(new Properties());
public static GemFireMockObjectsBeanPostProcessor newInstance() {
return newInstance(DEFAULT_USE_SINGLETON_CACHE);
}
public static GemFireMockObjectsBeanPostProcessor newInstance(boolean useSingletonCache) {
GemFireMockObjectsBeanPostProcessor beanPostProcessor = new GemFireMockObjectsBeanPostProcessor();
beanPostProcessor.useSingletonCache = useSingletonCache;
return beanPostProcessor;
}
@Nullable @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return (isGemFireProperties(bean, beanName) ? set((Properties) bean)
: (bean instanceof CacheFactoryBean ? spyOnCacheFactoryBean((CacheFactoryBean) bean, this.useSingletonCache)
: (bean instanceof PoolFactoryBean ? mockThePoolFactoryBean((PoolFactoryBean) bean)
: bean)));
}
@Nullable @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof GemFireCache) {
GemFireCache gemfireCache = (GemFireCache) bean;
when(gemfireCache.getDistributedSystem().getProperties()).thenReturn(this.gemfireProperties.get());
}
return bean;
}
private boolean isGemFireProperties(Object bean, String beanName) {
return bean instanceof Properties && GEMFIRE_PROPERTIES_BEAN_NAME.equals(beanName);
}
private Object set(Properties gemfireProperties) {
this.gemfireProperties.set(gemfireProperties);
return gemfireProperties;
}
private Object spyOnCacheFactoryBean(CacheFactoryBean bean, boolean useSingletonCache) {
return bean instanceof ClientCacheFactoryBean
? SpyingClientCacheFactoryInitializer.spyOn((ClientCacheFactoryBean) bean, useSingletonCache)
: SpyingCacheFactoryInitializer.spyOn(bean, useSingletonCache);
}
private Object mockThePoolFactoryBean(PoolFactoryBean bean) {
return MockingPoolFactoryInitializer.mock(bean);
}
protected static class SpyingCacheFactoryInitializer
implements CacheFactoryBean.CacheFactoryInitializer<CacheFactory> {
protected static CacheFactoryBean spyOn(CacheFactoryBean cacheFactoryBean, boolean useSingletonCache) {
cacheFactoryBean.setCacheFactoryInitializer(new SpyingCacheFactoryInitializer(useSingletonCache));
return cacheFactoryBean;
}
private final boolean useSingletonCache;
protected SpyingCacheFactoryInitializer(boolean useSingletonCache) {
this.useSingletonCache = useSingletonCache;
}
protected boolean isUsingSingletonCache() {
return this.useSingletonCache;
}
@Override
public CacheFactory initialize(CacheFactory cacheFactory) {
return GemFireMockObjectsSupport.spyOn(cacheFactory, isUsingSingletonCache());
}
}
protected static class SpyingClientCacheFactoryInitializer
implements CacheFactoryBean.CacheFactoryInitializer<ClientCacheFactory> {
protected static ClientCacheFactoryBean spyOn(ClientCacheFactoryBean clientCacheFactoryBean,
boolean useSingletonCache) {
clientCacheFactoryBean.setCacheFactoryInitializer(
new SpyingClientCacheFactoryInitializer(useSingletonCache));
return clientCacheFactoryBean;
}
private final boolean useSingletonCache;
protected SpyingClientCacheFactoryInitializer(boolean useSingletonCache) {
this.useSingletonCache = useSingletonCache;
}
protected boolean isUsingSingletonCache() {
return this.useSingletonCache;
}
@Override
public ClientCacheFactory initialize(ClientCacheFactory clientCacheFactory) {
return GemFireMockObjectsSupport.spyOn(clientCacheFactory, isUsingSingletonCache());
}
}
protected static class MockingPoolFactoryInitializer implements PoolFactoryBean.PoolFactoryInitializer {
protected static PoolFactoryBean mock(PoolFactoryBean poolFactoryBean) {
poolFactoryBean.setPoolFactoryInitializer(new MockingPoolFactoryInitializer());
return poolFactoryBean;
}
@Override
public PoolFactory initialize(PoolFactory poolFactory) {
return GemFireMockObjectsSupport.mockPoolFactory();
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.context;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.gemfire.tests.mock.config.GemFireMockObjectsBeanPostProcessor;
/**
* The {@link GemFireMockObjectsApplicationContextInitializer} class is a Spring {@link ApplicationContextInitializer}
* used to initialize the Spring {@link ConfigurableApplicationContext} with GemFire Object mocking.
*
* @author John Blum
* @see org.springframework.context.ApplicationContextInitializer
* @see org.springframework.context.ConfigurableApplicationContext
* @see org.springframework.data.gemfire.tests.mock.config.GemFireMockObjectsBeanPostProcessor
* @since 0.0.1
*/
public class GemFireMockObjectsApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
@SuppressWarnings("all")
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().addBeanPostProcessor(GemFireMockObjectsBeanPostProcessor.newInstance());
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.support;
import java.lang.reflect.Method;
/**
* The {@link MockObjectInvocationException} class is an extension of {@link MockObjectsException} to categorize
* problems with {@link Method method} invocations on {@link Object Mock Objects}.
*
* @author John Blum
* @see org.springframework.data.gemfire.tests.mock.support.MockObjectsException
* @since 0.0.1
*/
@SuppressWarnings("unused")
public class MockObjectInvocationException extends MockObjectsException {
/**
* Constructs a new instance of the {@link MockObjectInvocationException} class with no message or underlying cause.
*/
public MockObjectInvocationException() {
}
/**
* Constructs a new instance of the {@link MockObjectInvocationException} class initialized with
* the given {@link String message} describing the problem.
*
* @param message {@link String} describing the problem.
* @see String
*/
public MockObjectInvocationException(String message) {
super(message);
}
/**
* Constructs a new instance of the {@link MockObjectInvocationException} class initialized with
* the given {@link Throwable cause} of the underlying problem.
*
* @param cause {@link Throwable} object containing the cause of this exception.
* @see Throwable
*/
public MockObjectInvocationException(Throwable cause) {
super(cause);
}
/**
* Constructs a new instance of the {@link MockObjectInvocationException} class initialized with
* the given {@link String message} describing the underlying problem as well as the {@link Throwable cause}
* of the underlying problem.
*
* @param message {@link String} describing the problem.
* @param cause {@link Throwable} object containing the cause of this exception.
* @see Throwable
* @see String
*/
public MockObjectInvocationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock.support;
/**
* The {@link MockObjectsException} class is a {@link RuntimeException} indicating a general problem
* with the Mock Objects infrastructure.
*
* @author John Blum
* @see RuntimeException
* @since 0.0.1
*/
@SuppressWarnings("unused")
public class MockObjectsException extends RuntimeException {
/**
* Constructs a new instance of the {@link MockObjectsException} class with no message or underlying cause.
*/
public MockObjectsException() {
}
/**
* Constructs a new instance of the {@link MockObjectsException} class initialized with
* the given {@link String message} describing the problem.
*
* @param message {@link String} describing the problem.
* @see String
*/
public MockObjectsException(String message) {
super(message);
}
/**
* Constructs a new instance of the {@link MockObjectsException} class initialized with
* the given {@link Throwable cause} of the underlying problem.
*
* @param cause {@link Throwable} object containing the cause of this exception.
* @see Throwable
*/
public MockObjectsException(Throwable cause) {
super(cause);
}
/**
* Constructs a new instance of the {@link MockObjectsException} class initialized with
* the given {@link String message} describing the underlying problem as well as the {@link Throwable cause}
* of the underlying problem.
*
* @param message {@link String} describing the problem.
* @param cause {@link Throwable} object containing the cause of this exception.
* @see Throwable
* @see String
*/
public MockObjectsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
/**
* The {@link PidNotFoundException} class is a {@link RuntimeException} indicating that the process ID (PID)
* is unobtainable for the current {@link Process}.
*
* @author John Blum
* @see java.lang.RuntimeException
* @since 0.0.1
*/
@SuppressWarnings("unused")
public class PidNotFoundException extends RuntimeException {
public PidNotFoundException() {
}
public PidNotFoundException(final String message) {
super(message);
}
public PidNotFoundException(final Throwable cause) {
super(cause);
}
public PidNotFoundException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.gemfire.tests.util.FileSystemUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The {@link ProcessConfiguration} class is a container encapsulating configuration and context meta-data
* for a running process.
*
* @author John Blum
* @see java.lang.Process
* @see java.lang.ProcessBuilder
* @see org.springframework.data.gemfire.tests.process.ProcessExecutor
* @since 0.0.1
*/
@SuppressWarnings("unused")
public class ProcessConfiguration {
private final boolean redirectingErrorStream;
private final File workingDirectory;
private final List<String> command;
private final Map<String, String> environment;
public static ProcessConfiguration create(ProcessBuilder processBuilder) {
Assert.notNull(processBuilder,
"The ProcessBuilder used to configure and start the Process must not be null");
return new ProcessConfiguration(processBuilder.command(), processBuilder.directory(),
processBuilder.environment(), processBuilder.redirectErrorStream());
}
public ProcessConfiguration(List<String> command, File workingDirectory, Map<String, String> environment,
boolean redirectErrorStream) {
Assert.notEmpty(command, "Process command is required");
Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory),
String.format("Process working directory [%s] is not valid", workingDirectory));
this.command = Collections.unmodifiableList(new ArrayList<>(command));
this.workingDirectory = workingDirectory;
this.redirectingErrorStream = redirectErrorStream;
this.environment = environment != null
? Collections.unmodifiableMap(new HashMap<>(environment))
: Collections.emptyMap();
}
public List<String> getCommand() {
return this.command;
}
public String getCommandString() {
return StringUtils.arrayToDelimitedString(getCommand().toArray(), " ");
}
public Map<String, String> getEnvironment() {
return this.environment;
}
public boolean isRedirectingErrorStream() {
return this.redirectingErrorStream;
}
public File getWorkingDirectory() {
return this.workingDirectory;
}
@Override
public String toString() {
return "{ command = ".concat(getCommandString())
.concat(", workingDirectory = ".concat(getWorkingDirectory().getAbsolutePath()))
.concat(", environment = ".concat(String.valueOf(getEnvironment())))
.concat(", redirectingErrorStream = ".concat(String.valueOf(isRedirectingErrorStream())))
.concat(" }");
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.gemfire.tests.util.FileSystemUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The {@link ProcessExecutor} class is a utility class for launching and running Java processes.
*
* @author John Blum
* @see java.io.File
* @see java.lang.Process
* @see java.lang.ProcessBuilder
* @see java.lang.System
* @see org.springframework.data.gemfire.tests.process.ProcessConfiguration
* @see org.springframework.data.gemfire.tests.process.ProcessWrapper
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ProcessExecutor {
public static final File JAVA_EXE = new File(new File(FileSystemUtils.JAVA_HOME, "bin"), "java");
public static final String JAVA_CLASSPATH = System.getProperty("java.class.path");
protected static final String SPRING_DATA_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.data.gemfire.";
protected static final String SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.gemfire.";
public static ProcessWrapper launch(Class<?> type, String... args) throws IOException {
return launch(FileSystemUtils.WORKING_DIRECTORY, type, args);
}
public static ProcessWrapper launch(File workingDirectory, Class<?> type, String... args) throws IOException {
return launch(workingDirectory, JAVA_CLASSPATH, type, args);
}
public static ProcessWrapper launch(File workingDirectory, String classpath, Class<?> type, String... args)
throws IOException {
ProcessBuilder processBuilder = new ProcessBuilder()
.command(buildCommand(classpath, type, args))
.directory(validateDirectory(workingDirectory))
.redirectErrorStream(true);
Process process = processBuilder.start();
ProcessWrapper processWrapper = new ProcessWrapper(process, ProcessConfiguration.create(processBuilder));
processWrapper.register((input) -> System.err.printf("[FORK] - %s%n", input));
return processWrapper;
}
protected static String[] buildCommand(String classpath, Class<?> type, String... args) {
Assert.notNull(type, "The main Java class to launch must not be null");
List<String> command = new ArrayList<>();
List<String> programArguments = new ArrayList<>(args.length);
command.add(JAVA_EXE.getAbsolutePath());
command.add("-server");
command.add("-ea");
command.add("-classpath");
command.add(StringUtils.hasText(classpath) ? classpath : JAVA_CLASSPATH);
command.addAll(getSpringGemFireSystemProperties());
for (String arg : nullSafeArray(args, String.class)) {
if (isJvmOption(arg)) {
command.add(arg);
}
else if (isValidArgument(arg)) {
programArguments.add(arg);
}
}
command.add(type.getName());
command.addAll(programArguments);
return command.toArray(new String[command.size()]);
}
protected static Collection<? extends String> getSpringGemFireSystemProperties() {
return System.getProperties().stringPropertyNames().stream()
.filter(property -> property.startsWith(SPRING_DATA_GEMFIRE_SYSTEM_PROPERTY_PREFIX)
|| property.startsWith(SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX))
.map(property -> String.format("-D%1$s=%2$s", property, System.getProperty(property)))
.collect(Collectors.toList());
}
protected static boolean isJvmOption(String option) {
return (StringUtils.hasText(option) && (option.startsWith("-D") || option.startsWith("-X")));
}
protected static boolean isValidArgument(String argument) {
return StringUtils.hasText(argument);
}
protected static File validateDirectory(File workingDirectory) {
Assert.isTrue(workingDirectory != null && (workingDirectory.isDirectory() || workingDirectory.mkdirs()),
String.format("Failed to create working directory [%s]", workingDirectory));
return workingDirectory;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
import java.util.EventListener;
/**
* The {@link ProcessInputStreamListener} is a callback interface that gets called when input arrives from either a
* {@link Process process's} standard output steam or standard error stream.
*
* @author John Blum
* @see java.util.EventListener
* @since 0.0.1
*/
public interface ProcessInputStreamListener extends EventListener {
/**
* Callback method that gets called when the {@link Process} sends output from either its standard out
* or standard error streams.
*
* @param input {@link String} containing output from the {@link Process} that this listener is listening to.
* @see java.lang.Process#getErrorStream()
* @see java.lang.Process#getInputStream()
*/
void onInput(String input);
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.Scanner;
import java.util.logging.Logger;
import org.springframework.data.gemfire.tests.util.FileSystemUtils;
import org.springframework.data.gemfire.tests.util.IOUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The {@link ProcessUtils} class is a utility class for working with Operating System (OS) {@link Process processes}.
*
* @author John Blum
* @see File
* @see Process
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ProcessUtils {
protected static final Logger log = Logger.getLogger(ProcessUtils.class.getName());
protected static final String TERM_TOKEN = "<TERM/>";
/* (non-Javadoc) */
public static int currentPid() {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
String runtimeMXBeanName = runtimeMXBean.getName();
Exception cause = null;
if (StringUtils.hasText(runtimeMXBeanName)) {
int atSignIndex = runtimeMXBeanName.indexOf('@');
if (atSignIndex > 0) {
try {
return Integer.parseInt(runtimeMXBeanName.substring(0, atSignIndex));
}
catch (NumberFormatException e) {
cause = e;
}
}
}
throw new PidNotFoundException(String.format("Process ID (PID) not available [%s]",
runtimeMXBeanName), cause);
}
public static boolean isAlive(Process process) {
return process != null && process.isAlive();
}
public static boolean isRunning(int processId) {
throw new UnsupportedOperationException("Operation not supported");
}
public static boolean isRunning(Process process) {
try {
process.exitValue();
return false;
}
catch (IllegalThreadStateException ignore) {
return true;
}
}
public static int findAndReadPid(File workingDirectory) {
File pidFile = findPidFile(workingDirectory);
if (pidFile == null) {
throw new PidNotFoundException(
String.format("No PID file was found in working directory [%s] or any of it's sub-directories",
workingDirectory));
}
return readPid(pidFile);
}
@SuppressWarnings("all")
protected static File findPidFile(File workingDirectory) {
Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory),
String.format("File [%s] is not a valid directory", workingDirectory));
for (File file : workingDirectory.listFiles(DirectoryPidFileFilter.INSTANCE)) {
if (file.isDirectory()) {
file = findPidFile(file);
}
if (PidFileFilter.INSTANCE.accept(file)) {
return file;
}
}
return null;
}
@SuppressWarnings("all")
public static int readPid(File pidFile) {
Assert.isTrue(pidFile != null && pidFile.isFile(),
String.format("File [%s] is not a valid file", pidFile));
BufferedReader fileReader = null;
String pidValue = null;
try {
fileReader = new BufferedReader(new FileReader(pidFile));
pidValue = String.valueOf(fileReader.readLine()).trim();
return Integer.parseInt(pidValue);
}
catch (FileNotFoundException cause) {
throw new PidNotFoundException(String.format("PID file [%s] not found", pidFile), cause);
}
catch (IOException cause) {
throw new PidNotFoundException(String.format("Failed to read PID from file [%s]", pidFile), cause);
}
catch (NumberFormatException cause) {
throw new PidNotFoundException(String.format("Value [%1$s] from PID file [%2$s] was not a valid numerical PID",
pidValue, pidFile), cause);
}
finally {
IOUtils.close(fileReader);
}
}
@SuppressWarnings("all")
public static void writePid(File pidFile, int pid) throws IOException {
Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()),
String.format("File [%s] is not a valid file", pidFile));
Assert.isTrue(pid > 0, String.format("PID [%d] must greater than 0", pid));
PrintWriter fileWriter = new PrintWriter(new BufferedWriter(
new FileWriter(pidFile, false), 16), true);
try {
fileWriter.println(pid);
}
finally {
pidFile.deleteOnExit();
FileSystemUtils.close(fileWriter);
}
}
public static void signalStop(Process process) throws IOException {
if (isRunning(process)) {
OutputStream processOutputStream = process.getOutputStream();
processOutputStream.write(TERM_TOKEN.concat("\n").getBytes());
processOutputStream.flush();
}
}
@SuppressWarnings("all")
public static void waitForStopSignal() {
Scanner in = new Scanner(System.in);
while (!TERM_TOKEN.equals(in.next()));
}
protected static class DirectoryPidFileFilter extends PidFileFilter {
protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter();
@Override
public boolean accept(File path) {
return (path != null && (path.isDirectory() || super.accept(path)));
}
}
protected static class PidFileFilter implements FileFilter {
protected static final PidFileFilter INSTANCE = new PidFileFilter();
@Override
public boolean accept(File path) {
return (path != null && path.isFile() && path.getName().toLowerCase().endsWith(".pid"));
}
}
}

View File

@@ -0,0 +1,340 @@
/*
* Copyright 2018 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.data.gemfire.tests.process;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.data.gemfire.tests.util.FileSystemUtils;
import org.springframework.data.gemfire.tests.util.FileUtils;
import org.springframework.data.gemfire.tests.util.IOUtils;
import org.springframework.data.gemfire.tests.util.ThreadUtils;
import org.springframework.data.gemfire.tests.util.ThrowableUtils;
import org.springframework.util.Assert;
/**
* The ProcessWrapper class is a wrapper for a Process object representing an OS process and the ProcessBuilder used
* to construct and start the process.
*
* @author John Blum
* @see Process
* @see ProcessBuilder
* @since 0.0.1
*/
@SuppressWarnings("unused")
public class ProcessWrapper {
protected static final boolean DEFAULT_DAEMON_THREAD = true;
protected static final long DEFAULT_WAIT_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(15);
private final List<ProcessInputStreamListener> listeners = new CopyOnWriteArrayList<>();
protected final Logger log = Logger.getLogger(getClass().getName());
private final Process process;
private final ProcessConfiguration processConfiguration;
public ProcessWrapper(Process process, ProcessConfiguration processConfiguration) {
Assert.notNull(process, "Process is required");
Assert.notNull(processConfiguration, "The context and configuration meta-data providing details"
+ " about the environment in which the process is running and how the process was configured and executed"
+ " is required");
this.process = process;
this.processConfiguration = processConfiguration;
init();
}
private void init() {
newThread("Process OUT Stream Reader Thread",
newProcessInputStreamReaderRunnable(process.getInputStream())).start();
if (!isRedirectingErrorStream()) {
newThread("Process ERR Stream Reader Thread",
newProcessInputStreamReaderRunnable(process.getErrorStream())).start();
}
}
private Runnable newProcessInputStreamReaderRunnable(InputStream in) {
return () -> {
if (isRunning()) {
BufferedReader inputReader = new BufferedReader(new InputStreamReader(in));
try {
for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) {
for (ProcessInputStreamListener listener : this.listeners) {
listener.onInput(input);
}
}
}
catch (IOException ignore) {
// Ignore IO error and just stop reading from the process input stream
// An IO error occurred most likely because the process was terminated
}
finally {
IOUtils.close(inputReader);
}
}
};
}
private Thread newThread(String name, Runnable task) {
Assert.hasText(name, "Thread name is required");
Assert.notNull(task, "Thread task is required");
Thread thread = new Thread(task, name);
thread.setDaemon(DEFAULT_DAEMON_THREAD);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
public boolean isAlive() {
return ProcessUtils.isAlive(process);
}
public boolean isNotAlive() {
return !isAlive();
}
public List<String> getCommand() {
return this.processConfiguration.getCommand();
}
public String getCommandString() {
return this.processConfiguration.getCommandString();
}
public Map<String, String> getEnvironment() {
return this.processConfiguration.getEnvironment();
}
public int getPid() {
return ProcessUtils.findAndReadPid(getWorkingDirectory());
}
public int safeGetPid() {
try {
return getPid();
}
catch (PidNotFoundException ignore) {
return -1;
}
}
public boolean isRedirectingErrorStream() {
return this.processConfiguration.isRedirectingErrorStream();
}
public boolean isNotRunning() {
return !isRunning();
}
public boolean isRunning() {
return ProcessUtils.isRunning(this.process);
}
public File getWorkingDirectory() {
return this.processConfiguration.getWorkingDirectory();
}
public int exitValue() {
return this.process.exitValue();
}
public int safeExitValue() {
try {
return exitValue();
}
catch (IllegalThreadStateException ignore) {
return -1;
}
}
public String readLogFile() throws IOException {
File[] logFiles = FileSystemUtils.listFiles(getWorkingDirectory(),
path -> (path != null && (path.isDirectory() || path.getAbsolutePath().endsWith(".log"))));
if (logFiles.length > 0) {
return readLogFile(logFiles[0]);
}
else {
throw new FileNotFoundException(String.format("No log files found in process's [%d] working directory [%s]",
safeGetPid(), getWorkingDirectory()));
}
}
public String readLogFile(File log) throws IOException {
return FileUtils.read(log);
}
public boolean register(ProcessInputStreamListener listener) {
return listener != null && listeners.add(listener);
}
public void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}
public void signal() {
try {
OutputStream outputStream = this.process.getOutputStream();
outputStream.write("\n".getBytes());
outputStream.flush();
}
catch (IOException cause) {
this.log.warning("Failed to signal process");
if (this.log.isLoggable(Level.FINE)) {
this.log.fine(ThrowableUtils.toString(cause));
}
}
}
/* (non-Javadoc) */
public void signalStop() {
try {
ProcessUtils.signalStop(this.process);
}
catch (IOException cause) {
this.log.warning("Failed to signal the process to stop");
if (this.log.isLoggable(Level.FINE)) {
this.log.fine(ThrowableUtils.toString(cause));
}
}
}
public int stop() {
return stop(DEFAULT_WAIT_TIME_MILLISECONDS);
}
public int stop(long milliseconds) {
if (isRunning()) {
boolean interrupted = false;
int exitValue = -1;
int pid = safeGetPid();
long timeout = (System.currentTimeMillis() + milliseconds);
AtomicBoolean exited = new AtomicBoolean(false);
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
Future<Integer> futureExitValue = executorService.submit(() -> {
this.process.destroy();
int localExitValue = this.process.waitFor();
exited.set(true);
return localExitValue;
});
while (!exited.get() && System.currentTimeMillis() < timeout) {
try {
exitValue = futureExitValue.get(milliseconds, TimeUnit.MILLISECONDS);
this.log.info(String.format("Process [%s] has stopped%n", pid));
}
catch (InterruptedException ignore) {
interrupted = true;
}
}
}
catch (TimeoutException cause) {
exitValue = -1;
this.log.warning(String.format("Process [%1$d] did not stop within the allotted timeout of %2$d seconds%n",
pid, TimeUnit.MILLISECONDS.toSeconds(milliseconds)));
}
catch (Exception ignore) {
// handles CancellationException, ExecutionException
}
finally {
executorService.shutdownNow();
if (interrupted) {
Thread.currentThread().interrupt();
}
}
return exitValue;
}
else {
return exitValue();
}
}
public int shutdown() {
if (isRunning()) {
this.log.info(String.format("Stopping process [%d]...%n", safeGetPid()));
signalStop();
waitFor();
}
return stop();
}
public boolean unregister(ProcessInputStreamListener listener) {
return this.listeners.remove(listener);
}
public void waitFor() {
waitFor(DEFAULT_WAIT_TIME_MILLISECONDS);
}
public void waitFor(long milliseconds) {
ThreadUtils.timedWait(milliseconds, 500, this::isRunning);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2018 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.data.gemfire.tests.support;
import java.util.Properties;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.ResourcePermission;
import org.apache.shiro.authz.AuthorizationException;
/**
* The {@link AbstractSecurityManager} class is an abstract base class supporting implementations of
* {@link org.apache.geode.security.SecurityManager}.
*
* @author John Blum
* @see org.apache.geode.security.SecurityManager
* @since 1.0.0
*/
@SuppressWarnings("unused")
public abstract class AbstractSecurityManager implements org.apache.geode.security.SecurityManager {
@Override
public void init(Properties securityProps) { }
@Override
public Object authenticate(Properties credentials) throws AuthenticationFailedException {
throw new AuthenticationFailedException("Access Denied");
}
@Override
public boolean authorize(Object principal, ResourcePermission permission) {
throw new AuthorizationException("Not Authorized");
}
@Override
public void close() { }
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2018 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.data.gemfire.tests.support;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
/**
* The {@link DataSourceAdapter} class is an implementation of the {@link DataSource} interface
* with unsupported operations by default.
*
* @author John Blum
* @see java.sql.Connection
* @see javax.sql.DataSource
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class DataSourceAdapter implements DataSource {
private static final String UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE = "Not Implemented";
@Override
public Connection getConnection() throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public Connection getConnection(final String username, final String password) throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public void setLogWriter(final PrintWriter out) throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public int getLoginTimeout() throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public void setLoginTimeout(final int seconds) throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
//@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
@Override
public <T> T unwrap(final Class<T> iface) throws SQLException {
throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2018 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.data.gemfire.tests.support;
import java.util.concurrent.atomic.AtomicLong;
/**
* The {@link IdentifierSequence} class is an Identifier (ID) generator generating unique IDs in sequence.
*
* @author John Blum
* @see java.lang.System#currentTimeMillis()
* @see java.util.concurrent.atomic.AtomicLong
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class IdentifierSequence {
private static final AtomicLong ID_SEQUENCE = new AtomicLong(System.currentTimeMillis());
public static long nextId() {
return ID_SEQUENCE.incrementAndGet();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2018 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.data.gemfire.tests.support;
import java.util.HashMap;
import java.util.Map;
/**
* The {@link MapBuilder} class employs the Builder Software Design Pattern to build a {@link Map}.
*
* @author John Blum
* @see Map
* @since 0.0.1
*/
public class MapBuilder<KEY, VALUE> {
public static <KEY, VALUE> MapBuilder<KEY, VALUE> newMapBuilder() {
return new MapBuilder<>();
}
private final Map<KEY, VALUE> map = new HashMap<>();
public MapBuilder<KEY, VALUE> put(KEY key, VALUE value) {
this.map.put(key, value);
return this;
}
public MapBuilder<KEY, VALUE> remove(KEY key) {
this.map.remove(key);
return this;
}
public Map<KEY, VALUE> build() {
return this.map;
}
}

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.Assert;
/**
* The {@link FileSystemUtils} class is a utility class encapsulating functionality to process
* file system directories and files collectively.
*
* @author John Blum
* @see java.io.File
* @see java.io.FileFilter
* @see org.springframework.data.gemfire.tests.util.FileUtils
* @see org.springframework.data.gemfire.tests.util.IOUtils
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class FileSystemUtils extends FileUtils {
public static final File JAVA_HOME = new File(System.getProperty("java.home"));
public static final File JAVA_EXE = new File(new File(JAVA_HOME, "bin"), "java");
public static final File TEMPORARY_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));
public static final File USER_HOME = new File(System.getProperty("user.home"));
public static final File WORKING_DIRECTORY = new File(System.getProperty("user.dir"));
public static final File[] NO_FILES = new File[0];
public static boolean deleteRecursive(File path) {
return deleteRecursive(path, AllFilesFilter.INSTANCE);
}
public static boolean deleteRecursive(File path, FileFilter fileFilter) {
boolean success = true;
if (isDirectory(path)) {
for (File file : safeListFiles(path, fileFilter)) {
success &= deleteRecursive(file);
}
}
return ((!exists(path) || path.delete()) && success);
}
public static boolean exists(File path) {
return path != null && path.exists();
}
// returns sub-directory just below working directory
public static File getRootRelativeToWorkingDirectoryOrPath(File path) {
File localPath = path;
if (isDirectory(localPath)) {
while (localPath != null && !WORKING_DIRECTORY.equals(localPath.getParentFile())) {
localPath = localPath.getParentFile();
}
}
return (localPath != null ? localPath : path);
}
public static File[] listFiles(File directory, FileFilter fileFilter) {
Assert.isTrue(isDirectory(directory),
String.format("File [%s] does not refer to a valid directory", directory));
List<File> results = new ArrayList<File>();
for (File file : safeListFiles(directory, fileFilter)) {
if (isDirectory(file)) {
results.addAll(Arrays.asList(listFiles(file, fileFilter)));
}
else {
results.add(file);
}
}
return results.toArray(new File[results.size()]);
}
public static File[] safeListFiles(File directory) {
return safeListFiles(directory, AllFilesFilter.INSTANCE);
}
public static File[] safeListFiles(File directory, FileFilter fileFilter) {
FileFilter resolvedFileFilter = (fileFilter != null ? fileFilter : AllFilesFilter.INSTANCE);
File[] files = (isDirectory(directory) ? directory.listFiles(resolvedFileFilter) : null);
return (files != null ? files : NO_FILES);
}
public static class AllFilesFilter implements FileFilter {
public static final AllFilesFilter INSTANCE = new AllFilesFilter();
@Override
public boolean accept(File pathname) {
return true;
}
}
public static class CompositeFileFilter implements FileFilter {
private final FileFilter fileFilterOne;
private final FileFilter fileFilterTwo;
private final LogicalOperator logicalOperator;
private CompositeFileFilter(FileFilter fileFilterOne, LogicalOperator operator, FileFilter fileFilterTwo) {
this.fileFilterOne = fileFilterOne;
this.logicalOperator = operator;
this.fileFilterTwo = fileFilterTwo;
}
protected static FileFilter compose(FileFilter fileFilterOne, LogicalOperator operator, FileFilter fileFilterTwo) {
return (fileFilterOne == null ? fileFilterTwo : (fileFilterTwo == null ? fileFilterOne
: new CompositeFileFilter(fileFilterOne, operator, fileFilterTwo)));
}
public static FileFilter and(FileFilter... fileFilters) {
return and(Arrays.asList(nullSafeArray(fileFilters, FileFilter.class)));
}
public static FileFilter and(Iterable<FileFilter> fileFilters) {
FileFilter current = null;
for (FileFilter fileFilter : nullSafeIterable(fileFilters)) {
current = compose(current, LogicalOperator.AND, fileFilter);
}
return current;
}
public static FileFilter or(FileFilter... fileFilters) {
return or(Arrays.asList(nullSafeArray(fileFilters, FileFilter.class)));
}
public static FileFilter or(Iterable<FileFilter> fileFilters) {
FileFilter current = null;
for (FileFilter fileFilter : nullSafeIterable(fileFilters)) {
current = compose(current, LogicalOperator.OR, fileFilter);
}
return current;
}
@Override
public boolean accept(File pathname) {
switch (this.logicalOperator) {
case AND:
return (fileFilterOne.accept(pathname) && fileFilterTwo.accept(pathname));
case OR:
return (fileFilterOne.accept(pathname) || fileFilterTwo.accept(pathname));
default:
throw new UnsupportedOperationException(String.format(
"Logical operator [%s] is unsupported", this.logicalOperator));
}
}
enum LogicalOperator {
AND, OR;
}
}
public static class DirectoryOnlyFilter implements FileFilter {
public static final DirectoryOnlyFilter INSTANCE = new DirectoryOnlyFilter();
@Override
public boolean accept(File pathname) {
return isDirectory(pathname);
}
}
public static final class FileExtensionFilter extends FileOnlyFilter {
private final String fileExtension;
public static FileExtensionFilter newFileExtensionFilter(String fileExtension) {
return new FileExtensionFilter(fileExtension);
}
public FileExtensionFilter(String fileExtension) {
Assert.hasText(fileExtension, String.format("File extension [%s] must be specified", fileExtension));
this.fileExtension = fileExtension;
}
@Override
public boolean accept(File pathname) {
return (super.accept(pathname) && pathname.getAbsolutePath().toLowerCase().endsWith(this.fileExtension));
}
}
public static class FileOnlyFilter implements FileFilter {
public static final FileOnlyFilter INSTANCE = new FileOnlyFilter();
@Override
public boolean accept(File pathname) {
return isFile(pathname);
}
}
public static class NegatingFileFilter implements FileFilter {
private final FileFilter delegate;
public static NegatingFileFilter newNegatingFileFilter(FileFilter delegate) {
return new NegatingFileFilter(delegate);
}
public NegatingFileFilter(FileFilter delegate) {
Assert.notNull(delegate, "FileFilter must not be null");
this.delegate = delegate;
}
@Override
public boolean accept(File pathname) {
return !this.delegate.accept(pathname);
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The {@link FileUtils} class is an abstract utility class for processing file system files
* by working with {@link File} objects.
*
* @author John Blum
* @see java.io.File
* @see java.io.FileReader
* @see java.io.FileWriter
* @see org.springframework.data.gemfire.tests.util.IOUtils
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class FileUtils extends IOUtils {
public static final String FILE_SEPARATOR = System.getProperty("file.separator");
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
public static boolean isDirectory(File path) {
return path != null && path.isDirectory();
}
public static boolean isFile(File path) {
return path != null && path.isFile();
}
public static File newFile(String pathname) {
return new File(pathname);
}
public static File newFile(File parent, String pathname) {
return new File(parent, pathname);
}
@SuppressWarnings("all")
public static String read(File file) throws IOException {
Assert.isTrue(isFile(file), String.format("The File [%s] to read the contents from is not a valid file", file));
BufferedReader fileReader = new BufferedReader(new FileReader(file));
try {
StringBuilder buffer = new StringBuilder();
for (String line = fileReader.readLine(); line != null; line = fileReader.readLine()) {
buffer.append(line);
buffer.append(LINE_SEPARATOR);
}
return buffer.toString().trim();
}
finally {
close(fileReader);
}
}
/* (non-Javadoc) */
public static void write(File file, String contents) throws IOException {
Assert.notNull(file, "File is required");
Assert.isTrue(StringUtils.hasText(contents),
String.format("The contents for File [%1$s] cannot be null or empty", file));
BufferedWriter fileWriter = null;
try {
fileWriter = new BufferedWriter(new FileWriter(file));
fileWriter.write(contents);
fileWriter.flush();
}
finally {
IOUtils.close(fileWriter);
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@link IOUtils} class is an abstract utility class for working with IO operations.
*
* @author John Blum
* @see java.io.Closeable
* @see java.io.Serializable
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class IOUtils {
protected static final Logger log = Logger.getLogger(IOUtils.class.getName());
public static boolean close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
return true;
}
catch (IOException cause) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("Failed to close the Closeable object (%1$s) due to an I/O error:%n%2$s",
closeable, ThrowableUtils.toString(cause)));
}
}
}
return false;
}
/**
* Executes the given {@link IoExceptionThrowingOperation}, handling any {@link IOException IOExceptions} thrown
* during normal IO processing.
*
* @param operation {@link IoExceptionThrowingOperation} to execute.
* @return a boolean indicating whether the IO operation was successful, or {@literal false} if the IO operation
* threw an {@link IOException}.
* @see IOException
*/
public static boolean doSafeIo(IoExceptionThrowingOperation operation) {
try {
operation.doIo();
return true;
}
catch (IOException cause) {
return false;
}
}
@SuppressWarnings("unchecked")
public static <T> T deserializeObject(byte[] objectBytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(objectBytes);
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
}
finally {
IOUtils.close(objectInputStream);
}
}
public static byte[] serializeObject(Serializable obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
return byteArrayOutputStream.toByteArray();
}
finally {
IOUtils.close(objectOutputStream);
}
}
public interface IoExceptionThrowingOperation {
void doIo() throws IOException;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
/**
* {@link ObjectUtils} is a utility class for performing different opeations on {@link Object objects}.
*
* @author John Blum
* @see java.lang.Object
* @since 1.0.0
*/
@SuppressWarnings("all")
public abstract class ObjectUtils {
public static <T> T doOperationSafely(ExceptionThrowingOperation<T> operation) {
return doOperationSafely(operation, null);
}
public static <T> T doOperationSafely(ExceptionThrowingOperation<T> operation, T defaultValue) {
try {
return operation.doExceptionThrowingOperation();
}
catch (Exception ignore) {
return defaultValue;
}
}
public static <T> T rethrowAsRuntimeException(ExceptionThrowingOperation<T> operation) {
try {
return operation.doExceptionThrowingOperation();
}
catch (RuntimeException cause) {
throw cause;
}
catch (Throwable cause) {
throw new RuntimeException(cause);
}
}
@FunctionalInterface
public interface ExceptionThrowingOperation<T> {
T doExceptionThrowingOperation() throws Exception;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Logger;
/**
* {@link SocketUtils} is a utility class for managing {@link Socket} and {@link ServerSocket} objects.
*
* @author John Blum
* @see java.net.ServerSocket
* @see java.net.Socket
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class SocketUtils {
private static final Logger log = Logger.getLogger(SocketUtils.class.getName());
public static boolean close(Socket socket) {
try {
if (socket != null) {
socket.close();
return true;
}
}
catch (IOException ignore) {
log.warning(String.format("Failed to close Socket [%s]", socket));
log.warning(ThrowableUtils.toString(ignore));
}
return false;
}
public static boolean close(ServerSocket serverSocket) {
try {
if (serverSocket != null) {
serverSocket.close();
return true;
}
}
catch (IOException ignore) {
log.warning(String.format("Failed to close ServerSocket [%s]", serverSocket));
log.warning(ThrowableUtils.toString(ignore));
}
return false;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
/**
* The {@link StackTraceUtils} class is a utility class for working with stack trace frames (elements)
* of the current {@link Thread}.
*
* @author John Blum
* @see StackTraceElement
* @see Thread
* @see org.springframework.data.gemfire.tests.util.ThreadUtils
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class StackTraceUtils extends ThreadUtils {
public static StackTraceElement getCaller() {
return getCaller(Thread.currentThread());
}
public static StackTraceElement getCaller(final Thread thread) {
return thread.getStackTrace()[2];
}
public static String getCallerName(final StackTraceElement element) {
return String.format("%1$%s.%2$s", element.getClass().getName(), element.getMethodName());
}
public static String getCallerSimpleName(final StackTraceElement element) {
return String.format("%1$%s.%2$s", element.getClass().getSimpleName(), element.getMethodName());
}
public static StackTraceElement getTestCaller() {
return getTestCaller(Thread.currentThread());
}
public static StackTraceElement getTestCaller(final Thread thread) {
for (StackTraceElement stackTraceElement : thread.getStackTrace()) {
if (isTestSuiteClass(stackTraceElement) && isTestCaseMethod(stackTraceElement)) {
return stackTraceElement;
}
}
return null;
}
private static boolean isTestCaseMethod(final StackTraceElement element) {
boolean result = element.getMethodName().toLowerCase().startsWith("test");
try {
result |= element.getClass().getMethod(element.getMethodName()).isAnnotationPresent(org.junit.Test.class);
}
catch (NoSuchMethodException ignore) {
}
return result;
}
private static boolean isTestSuiteClass(final StackTraceElement element) {
boolean result = element.getClass().getSimpleName().toLowerCase().endsWith("test");
result |= element.getClass().isAssignableFrom(junit.framework.TestCase.class);
return result;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.util.concurrent.TimeUnit;
/**
* {@link ThreadUtils} is an abstract utility class for managing Java {@link Thread Threads}.
*
* @author John Blum
* @see java.lang.Thread
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ThreadUtils {
public static boolean sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
return true;
}
catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
return false;
}
}
public static boolean timedWait(long duration) {
return timedWait(duration, duration);
}
public static boolean timedWait(long duration, long interval) {
return timedWait(duration, interval, () -> true);
}
@SuppressWarnings("all")
public static boolean timedWait(long duration, long interval, WaitCondition waitCondition) {
final long timeout = System.currentTimeMillis() + duration;
interval = Math.min(interval, duration);
try {
while (waitCondition.waiting() && (System.currentTimeMillis() < timeout)) {
synchronized (waitCondition) {
TimeUnit.MILLISECONDS.timedWait(waitCondition, interval);
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return !waitCondition.waiting();
}
// TODO rename interface to Condition and waiting() method to evaluate()
public interface WaitCondition {
boolean waiting();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* The {@link ThrowableUtils} class is a utility class for working with {@link Throwable},
* {@link Exception} and {@link Error} objects.
*
* @author John Blum
* @see java.lang.Error
* @see java.lang.Exception
* @see java.lang.Throwable
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ThrowableUtils {
public static String toString(Throwable throwable) {
StringWriter writer = new StringWriter();
throwable.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2018 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.data.gemfire.tests.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
/**
* The {@link ZipUtils} class is an abstract utility class for working with JAR and ZIP archives.
*
* @author John Blum
* @see java.io.File
* @see java.util.zip.ZipFile
* @since 0.0.1
*/
@SuppressWarnings("unused")
public abstract class ZipUtils {
public static void unzip(final Resource zipResource, final File directory) throws IOException {
Assert.notNull(zipResource, "ZIP Resource is required");
Assert.isTrue(directory != null && directory.isDirectory(),
String.format("The file system pathname (%1$s) is not a valid directory!", directory));
ZipFile zipFile = new ZipFile(zipResource.getFile(), ZipFile.OPEN_READ);
for (ZipEntry entry : CollectionUtils.iterable(zipFile.entries())) {
if (entry.isDirectory()) {
new File(directory, entry.getName()).mkdirs();
}
else {
DataInputStream entryInputStream = new DataInputStream(zipFile.getInputStream(entry));
DataOutputStream entryOutputStream = new DataOutputStream(new FileOutputStream(
new File(directory, entry.getName())));
try {
FileCopyUtils.copy(entryInputStream, entryOutputStream);
}
finally {
IOUtils.close(entryInputStream);
IOUtils.close(entryOutputStream);
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2018 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.data.gemfire;
import static org.assertj.core.api.Assertions.assertThat;
import javax.annotation.Resource;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects;
import org.springframework.data.gemfire.util.RegionUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration tests for an Apache Geode {@link ClientCache} application using mock objects.
*
* @author John Blum
* @see org.junit.Test
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cahce.Region
* @see org.apache.geode.cache.client.ClientCache
* @see org.springframework.data.gemfire.client.ClientCacheFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
* @see org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringRunner
* @since 1.0.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@SuppressWarnings("all")
public class MockClientCacheApplicationIntegrationTests {
@Resource(name = "Example")
private Region<Object, Object> example;
@Test
public void exampleRegionIsMocked() {
assertThat(this.example).isNotNull();
assertThat(this.example.getFullPath()).isEqualTo(RegionUtils.toRegionPath("Example"));
assertThat(this.example.getName()).isEqualTo("Example");
assertThat(this.example.put(1, "test")).isNull();
assertThat(this.example.get(1)).isEqualTo("test");
}
@EnableGemFireMockObjects
@ClientCacheApplication
static class TestConfiguration {
@Bean("Example")
public ClientRegionFactoryBean<Object, Object> exampleRegion(GemFireCache gemfireCache) {
ClientRegionFactoryBean<Object, Object> exampleRegion = new ClientRegionFactoryBean<>();
exampleRegion.setCache(gemfireCache);
exampleRegion.setClose(false);
exampleRegion.setShortcut(ClientRegionShortcut.LOCAL);
return exampleRegion;
}
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2018 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.data.gemfire.tests.mock;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport;
import org.springframework.data.gemfire.tests.support.AbstractSecurityManager;
/**
* Integration tests for {@link GemFireMockObjectsSupport}.
*
* @author John Blum
* @see java.util.Properties
* @see org.junit.Test
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.CacheFactory
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport
* @since 1.0.0
*/
public class GemFireMockObjectsSupportIntegrationTests extends IntegrationTestsSupport {
@After
public void tearDown() {
GemFireMockObjectsSupport.destroy();
TestSecurityManager.constructed.set(false);
TestSecurityManager.destroyed.set(false);
TestSecurityPostProcessor.constructed.set(false);
}
@Test
public void constructsGemFireObjectsFromPropertiesSuccessfully() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "TestConstructsGemFireObjectsFromPropertiesSuccessfully");
gemfireProperties.setProperty("security-manager", TestSecurityManager.class.getName());
assertThat(TestSecurityManager.constructed.get()).isFalse();
GemFireMockObjectsSupport.spyOn(new CacheFactory(gemfireProperties)).create();
assertThat(TestSecurityManager.constructed.get()).isTrue();
}
@Test
public void destroysConstructedGemFireObjectsFromPropertiesSuccessfully() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "TestConstructsGemFireObjectsFromPropertiesSuccessfully");
gemfireProperties.setProperty("security-manager", TestSecurityManager.class.getName());
gemfireProperties.setProperty("security-post-processor", TestSecurityPostProcessor.class.getName());
assertThat(TestSecurityManager.constructed.get()).isFalse();
assertThat(TestSecurityManager.destroyed.get()).isFalse();
assertThat(TestSecurityPostProcessor.constructed.get()).isFalse();
GemFireMockObjectsSupport.spyOn(new CacheFactory(gemfireProperties)).create();
assertThat(TestSecurityManager.constructed.get()).isTrue();
assertThat(TestSecurityManager.destroyed.get()).isFalse();
assertThat(TestSecurityPostProcessor.constructed.get()).isTrue();
GemFireMockObjectsSupport.destroyGemFireObjects();
assertThat(TestSecurityManager.destroyed.get()).isTrue();
}
@Test
public void storesGemFirePropertiesSuccessfully() {
try {
System.setProperty("gemfire.name", "TestStoresGemFirePropertiesSuccessfully");
System.setProperty("gemfire.log-level", "config");
System.setProperty("gemfire.locators", "skullbox[12345]");
System.setProperty("non-gemfire.property", "test");
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("log-level", "info");
gemfireProperties.setProperty("jmx-manager-port", "1199");
gemfireProperties.setProperty("groups", "test,mock");
CacheFactory mockCacheFactory =
GemFireMockObjectsSupport.spyOn(new CacheFactory(gemfireProperties));
mockCacheFactory.set("groups", "qa,test,testers");
mockCacheFactory.set("conserve-sockets", "true");
Cache mockCache = mockCacheFactory.create();
assertThat(mockCache).isNotNull();
assertThat(mockCache.getDistributedSystem()).isNotNull();
Properties actualGemFireProperties = mockCache.getDistributedSystem().getProperties();
assertThat(actualGemFireProperties).isNotNull();
assertThat(actualGemFireProperties).hasSize(6);
assertThat(actualGemFireProperties.getProperty("name")).isEqualTo("TestStoresGemFirePropertiesSuccessfully");
assertThat(actualGemFireProperties.getProperty("log-level")).isEqualTo("config");
assertThat(actualGemFireProperties.getProperty("locators")).isEqualTo("skullbox[12345]");
assertThat(actualGemFireProperties.getProperty("jmx-manager-port")).isEqualTo("1199");
assertThat(actualGemFireProperties.getProperty("groups")).isEqualTo("qa,test,testers");
assertThat(actualGemFireProperties.getProperty("conserve-sockets")).isEqualTo("true");
}
finally {
System.clearProperty("gemfire.name");
System.clearProperty("gemfire.log-level");
System.clearProperty("non-gemfire.property");
}
}
public static final class TestSecurityManager extends AbstractSecurityManager implements DisposableBean {
private static final AtomicBoolean constructed = new AtomicBoolean(false);
private static final AtomicBoolean destroyed = new AtomicBoolean(false);
public TestSecurityManager() {
constructed.set(true);
}
@Override
public void destroy() throws Exception {
destroyed.set(true);
}
}
public static final class TestSecurityPostProcessor {
private static final AtomicBoolean constructed = new AtomicBoolean(false);
public TestSecurityPostProcessor() {
constructed.set(true);
}
}
}