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:
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(" }");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() { }
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user