From 750ecc57b2ae3e85453758b975e0c05d76d28741 Mon Sep 17 00:00:00 2001 From: John Blum Date: Sat, 2 Dec 2017 21:26:41 -0800 Subject: [PATCH] Initial spring-data-tests-4-gemfire project commit. --- .gitignore | 6 + LICENSE | 201 ++ pom.xml | 208 ++ .../ClientServerIntegrationTestsSupport.java | 328 +++ .../tests/mock/MockGemFireObjectsSupport.java | 1904 +++++++++++++++++ .../tests/mock/MockObjectsSupport.java | 189 ++ .../annotation/EnableGemFireMockObjects.java | 58 + .../GemFireMockObjectsConfiguration.java | 97 + .../mock/annotation/GemFireUnitTest.java | 58 + .../MockGemFireObjectsBeanPostProcessor.java | 173 ++ ...eObjectsApplicationContextInitializer.java | 41 + .../MockObjectInvocationException.java | 73 + .../mock/support/MockObjectsException.java | 71 + .../process/PidUnavailableException.java | 45 + .../tests/process/ProcessConfiguration.java | 102 + .../tests/process/ProcessExecutor.java | 125 ++ .../process/ProcessInputStreamListener.java | 41 + .../gemfire/tests/process/ProcessUtils.java | 233 ++ .../gemfire/tests/process/ProcessWrapper.java | 347 +++ .../tests/support/DataSourceAdapter.java | 84 + .../tests/support/IdentifierSequence.java | 38 + .../gemfire/tests/support/MapBuilder.java | 50 + .../gemfire/tests/util/FileSystemUtils.java | 249 +++ .../data/gemfire/tests/util/FileUtils.java | 106 + .../data/gemfire/tests/util/IOUtils.java | 96 + .../data/gemfire/tests/util/SocketUtils.java | 69 + .../gemfire/tests/util/StackTraceUtils.java | 79 + .../data/gemfire/tests/util/ThreadUtils.java | 79 + .../gemfire/tests/util/ThrowableUtils.java | 40 + .../data/gemfire/tests/util/ZipUtils.java | 70 + 30 files changed, 5260 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 src/main/java/org/springframework/data/gemfire/tests/integration/ClientServerIntegrationTestsSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/MockGemFireObjectsSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/MockObjectsSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/annotation/EnableGemFireMockObjects.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireUnitTest.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/config/MockGemFireObjectsBeanPostProcessor.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/context/MockGemFireObjectsApplicationContextInitializer.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectInvocationException.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectsException.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/support/DataSourceAdapter.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/support/IdentifierSequence.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/support/MapBuilder.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/FileSystemUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/FileUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/IOUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/SocketUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/ThreadUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/ThrowableUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/tests/util/ZipUtils.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e86ac30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.iml +*.ipr +*.iws +*.DS_Store +.idea/ +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..aa4b08b --- /dev/null +++ b/pom.xml @@ -0,0 +1,208 @@ + + + + 4.0.0 + + + org.springframework.data.build + spring-data-parent + 2.0.3.BUILD-SNAPSHOT + + + org.springframework.data + spring-data-tests-4-gemfire + 0.1.0.BUILD-SNAPSHOT + jar + + Spring Data Test Context Framework for Spring Data GemFire/Geode + + This project is a new Spring Data module building on Spring's TestContext Framework for writing Unit + and Integration Tests for both Spring Data for Pivotal GemFire as well as Spring Data for Apache Geode. + + https://github.com/jxblum/spring-data-tests-4-gemfire + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + Copyright 2017 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. + + + + + + Pivotal Software, Inc. + http://www.pivotal.io + + + + + jblum + John Blum + jblum at pivotal dot io + Pivotal Software, Inc. + http://www.spring.io + + Spring Data Team + Spring Data Cassandra Project Lead (Committer) + Spring Data GemFire Project Lead (Committer) + Spring Data Geode Project Lead (Committer) + Spring Session Data GemFire Project Lead (Committer) + Spring Session Data Geode Project Lead (Committer) + Spring Boot Data GemFire Project Lead (Committer) + Spring Boot Data Geode Project Lead (Committer) + Apache Geode Committer + + + + + + 1.8 + 3.3 + 2.19.1 + UTF-8 + 3.8.0 + 4.12 + 2.12.0 + 1.01 + ${project.parent.version} + ${project.parent.version} + 1.2.0.RELEASE + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + + + + + + org.assertj + assertj-core + ${assertj.version} + + + junit + junit + ${junit.version} + + + org.mockito + mockito-core + ${mockito.version} + + + org.projectlombok + lombok + ${lombok} + + + edu.umd.cs.mtc + multithreadedtc + ${multithreadedtc.version} + + + org.springframework.data + spring-data-gemfire + ${spring-data-gemfire.version} + + + org.springframework.data + spring-data-geode + ${spring-data-geode.version} + + + + + + + org.assertj + assertj-core + + + junit + junit + + + org.mockito + mockito-core + + + org.projectlombok + lombok + + + edu.umd.cs.mtc + multithreadedtc + + + org.springframework + spring-test + ${spring} + + + org.springframework.data + spring-data-geode + + + org.springframework.shell + spring-shell + ${spring-shell.version} + true + runtime + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + -Xlint:unchecked + + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + -Dgemfire.log-level=warning + + + **/*Test.java + **/*Tests.java + + false + + + + + + diff --git a/src/main/java/org/springframework/data/gemfire/tests/integration/ClientServerIntegrationTestsSupport.java b/src/main/java/org/springframework/data/gemfire/tests/integration/ClientServerIntegrationTestsSupport.java new file mode 100644 index 0000000..7e5f3c8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/integration/ClientServerIntegrationTestsSupport.java @@ -0,0 +1,328 @@ +/* + * Copyright 2016 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.TimeUnit; +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.annotation.AnnotationConfigApplicationContext + * @see org.springframework.data.gemfire.tests.process.ProcessExecutor + * @see org.springframework.data.gemfire.tests.process.ProcessWrapper + * @see SocketUtils + * @see ThreadUtils + * @since 0.0.1 + */ +@SuppressWarnings("unused") +public class ClientServerIntegrationTestsSupport { + + protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(30); + protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds + + 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 DEFAULT_HOSTNAME = "localhost"; + protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "spring.data.gemfire.directory.delete-on-exit"; + protected static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY = "spring.data.gemfire.cache.server.port"; + 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 = "warning"; + protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.log.level"; + 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 final String TEST_GEMFIRE_LOG_LEVEL = "warning"; + + /* (non-Javadoc) */ + protected static String asApplicationName(Class type) { + return type.getSimpleName(); + } + + /* (non-Javadoc) */ + 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"))); + } + + /* (non-Javadoc) */ + protected static File createDirectory(String pathname) { + return createDirectory(new File(pathname)); + } + + /* (non-Javadoc) */ + 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; + } + + /* (non-Javadoc) */ + protected static int findAvailablePort() throws IOException { + + ServerSocket serverSocket = null; + + try { + serverSocket = new ServerSocket(0); + return serverSocket.getLocalPort(); + } + finally { + SocketUtils.close(serverSocket); + } + } + + /* (non-Javadoc) */ + protected static String getClassNameAsPath(Class type) { + return type.getName().replaceAll("\\.", "/"); + } + + /* (non-Javadoc) */ + protected static String getClassNameAsPath(Object obj) { + return getClassNameAsPath(obj.getClass()); + } + + /* (non-Javadoc) */ + protected static String getPackageNameAsPath(Class type) { + return type.getPackage().getName().replaceAll("\\.", "/"); + } + + /* (non-Javadoc) */ + protected static String getPackageNameAsPath(Object obj) { + return getPackageNameAsPath(obj.getClass()); + } + + /* (non-Javadoc) */ + protected static String getContextXmlFileLocation(Class type) { + return getClassNameAsPath(type).concat("-context.xml"); + } + + /* (non-Javadoc) */ + protected static String getServerContextXmlFileLocation(Class type) { + return getClassNameAsPath(type).concat("-server-context.xml"); + } + + /* (non-Javadoc) */ + protected static boolean isDeleteDirectoryOnExit() { + return Boolean.valueOf(System.getProperty(DIRECTORY_DELETE_ON_EXIT_PROPERTY, Boolean.TRUE.toString())); + } + + /* (non-Javadoc) */ + protected static int intValue(Number number) { + return (number != null ? number.intValue() : 0); + } + + /* (non-Javadoc) */ + protected static String logFile() { + return logFile(GEMFIRE_LOG_FILE); + } + + /* (non-Javadoc) */ + protected static String logFile(String defaultLogFilePathname) { + return System.getProperty(GEMFIRE_LOG_FILE_PROPERTY, defaultLogFilePathname); + } + + /* (non-Javadoc) */ + protected static String logLevel() { + return logLevel(GEMFIRE_LOG_LEVEL); + } + + /* (non-Javadoc) */ + protected static String logLevel(String defaultLogLevel) { + return System.getProperty(GEMFIRE_LOG_LEVEL_PROPERTY, defaultLogLevel); + } + + /* (non-Javadoc) */ + protected static void logSystemProperties() throws IOException { + FileUtils.write(new File(SYSTEM_PROPERTIES_LOG_FILE), + String.format("%s", CollectionUtils.toString(System.getProperties()))); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(Class type, String... arguments) throws IOException { + return run(createDirectory(asDirectoryName(type)), type, arguments); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(File workingDirectory, Class type, String... arguments) throws IOException { + return (isProcessRunAuto() ? launch(createDirectory(workingDirectory), type, arguments) : null); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(String classpath, Class type, String... arguments) throws IOException { + return run(createDirectory(asDirectoryName(type)), classpath, type, arguments); + } + + /* (non-Javadoc) */ + protected static ProcessWrapper run(File workingDirectory, String classpath, Class type, String... arguments) + throws IOException { + + return (isProcessRunAuto() ? launch(createDirectory(workingDirectory), classpath, type, arguments) : null); + } + + /* (non-Javadoc) */ + protected static boolean isProcessRunAuto() { + return !isProcessRunManual(); + } + + /* (non-Javadoc) */ + protected static boolean isProcessRunManual() { + return Boolean.getBoolean(PROCESS_RUN_MANUAL_PROPERTY); + } + + /* (non-Javadoc) */ + protected static AnnotationConfigApplicationContext runSpringApplication(Class annotatedClass, String... args) { + return runSpringApplication(asArray(annotatedClass), args); + } + + /* (non-Javadoc) */ + protected static AnnotationConfigApplicationContext runSpringApplication(Class[] annotatedClasses, + String... args) { + + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(annotatedClasses); + + applicationContext.registerShutdownHook(); + + return applicationContext; + } + + /* (non-Javadoc) */ + protected static boolean stop(ProcessWrapper process) { + return stop(process, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + 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); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(CacheServer cacheServer) { + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) { + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); + } + + /* (non-Javadoc) */ + protected static boolean waitForServerToStart(String host, int port) { + return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + 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(); + } + }); + } + + 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 e) { + Thread.currentThread().interrupt(); + } + + return condition.evaluate(); + } + + protected interface Condition { + boolean evaluate(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/MockGemFireObjectsSupport.java b/src/main/java/org/springframework/data/gemfire/tests/mock/MockGemFireObjectsSupport.java new file mode 100644 index 0000000..d4dc21b --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/MockGemFireObjectsSupport.java @@ -0,0 +1,1904 @@ +/* + * Copyright 2017 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.NOT_SUPPORTED; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newUnsupportedOperationException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +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.Function; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.CacheListener; +import org.apache.geode.cache.CacheLoader; +import org.apache.geode.cache.CacheWriter; +import org.apache.geode.cache.CustomExpiry; +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.DiskStore; +import org.apache.geode.cache.DiskStoreFactory; +import org.apache.geode.cache.EvictionAttributes; +import org.apache.geode.cache.ExpirationAction; +import org.apache.geode.cache.ExpirationAttributes; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.PartitionAttributes; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionAttributes; +import org.apache.geode.cache.RegionExistsException; +import org.apache.geode.cache.RegionFactory; +import org.apache.geode.cache.RegionService; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.Scope; +import org.apache.geode.cache.SubscriptionAttributes; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientCacheFactory; +import org.apache.geode.cache.client.ClientRegionFactory; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.Pool; +import org.apache.geode.cache.client.PoolFactory; +import org.apache.geode.cache.control.ResourceManager; +import org.apache.geode.cache.execute.RegionFunctionContext; +import org.apache.geode.cache.query.CqAttributes; +import org.apache.geode.cache.query.CqQuery; +import org.apache.geode.cache.query.Query; +import org.apache.geode.cache.query.QueryService; +import org.apache.geode.cache.query.QueryStatistics; +import org.apache.geode.cache.server.CacheServer; +import org.apache.geode.cache.server.ClientSubscriptionConfig; +import org.apache.geode.compression.Compressor; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.DistributedSystem; +import org.apache.geode.internal.concurrent.ConcurrentHashSet; +import org.apache.geode.pdx.PdxSerializer; +import org.mockito.ArgumentMatchers; +import org.mockito.stubbing.Answer; +import org.springframework.data.gemfire.server.SubscriptionEvictionPolicy; +import org.springframework.data.gemfire.tests.mock.support.MockObjectInvocationException; +import org.springframework.data.gemfire.tests.util.FileSystemUtils; + +/** + * The {@link MockGemFireObjectsSupport} class is an abstract base class encapsulating factory methods for creating + * Mock GemFire Objects (e.g. {@link Cache}, {@link ClientCache}, {@link Region}, etc). + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.CacheFactory + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.ClientCacheFactory + * @see org.apache.geode.cache.control.ResourceManager + * @see org.apache.geode.cache.server.CacheServer + * @see org.apache.geode.cache.server.ClientSubscriptionConfig + * @see org.apache.geode.distributed.DistributedSystem + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.tests.mock.MockObjectsSupport + * @since 2.0.0 + */ +@SuppressWarnings("unused") +public abstract class MockGemFireObjectsSupport extends MockObjectsSupport { + + private static final boolean DEFAULT_USE_SINGLETON_CACHE = false; + + private static final AtomicReference singletonCache = new AtomicReference<>(null); + + private static final Map diskStores = new ConcurrentHashMap<>(); + + private static final Map> regions = new ConcurrentHashMap<>(); + + private static final Map> regionAttributes = new ConcurrentHashMap<>(); + + private static final String FROM_KEYWORD = "FROM"; + private static final String WHERE_KEYWORD = "WHERE"; + + private static final String REPEATING_REGION_SEPARATOR = Region.SEPARATOR + "{2,}"; + + /** + * Destroys all mock object state. + */ + public static void destroy() { + singletonCache.set(null); + diskStores.clear(); + regions.clear(); + regionAttributes.clear(); + } + + /** + * Converts the given {@link ClientRegionShortcut} into a corresponding {@link DataPolicy}. + * + * @param clientRegionShortcut {@link ClientRegionShortcut} to convert. + * @return a {@link DataPolicy} from the {@link ClientRegionShortcut}. + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.cache.DataPolicy + */ + @SuppressWarnings("unchecked") + private static DataPolicy convert(ClientRegionShortcut clientRegionShortcut) { + + return Optional.ofNullable(clientRegionShortcut).map(shortcut -> { + + switch(shortcut) { + case CACHING_PROXY: + case CACHING_PROXY_HEAP_LRU: + case CACHING_PROXY_OVERFLOW: + case LOCAL: + case LOCAL_HEAP_LRU: + case LOCAL_OVERFLOW: + return DataPolicy.NORMAL; + case LOCAL_PERSISTENT: + case LOCAL_PERSISTENT_OVERFLOW: + return DataPolicy.PERSISTENT_REPLICATE; + case PROXY: + return DataPolicy.EMPTY; + default: + return null; + } + + }).orElse(DataPolicy.DEFAULT); + } + + /** + * Converts the given {@link RegionShortcut} into a corresponding {@link DataPolicy}. + * + * @param regionShortcut {@link RegionShortcut} to convert. + * @return a {@link DataPolicy} from the {@link RegionShortcut}. + * @see org.apache.geode.cache.RegionShortcut + * @see org.apache.geode.cache.DataPolicy + */ + @SuppressWarnings("unchecked") + private static DataPolicy convert(RegionShortcut regionShortcut) { + + return Optional.ofNullable(regionShortcut).map(shortcut -> { + + switch (shortcut) { + case LOCAL: + case LOCAL_HEAP_LRU: + case LOCAL_OVERFLOW: + return DataPolicy.NORMAL; + case PARTITION: + case PARTITION_HEAP_LRU: + case PARTITION_OVERFLOW: + case PARTITION_PROXY: + case PARTITION_PROXY_REDUNDANT: + case PARTITION_REDUNDANT: + case PARTITION_REDUNDANT_HEAP_LRU: + case PARTITION_REDUNDANT_OVERFLOW: + return DataPolicy.PARTITION; + case PARTITION_PERSISTENT: + case PARTITION_PERSISTENT_OVERFLOW: + case PARTITION_REDUNDANT_PERSISTENT: + case PARTITION_REDUNDANT_PERSISTENT_OVERFLOW: + return DataPolicy.PERSISTENT_PARTITION; + case REPLICATE: + case REPLICATE_HEAP_LRU: + case REPLICATE_OVERFLOW: + return DataPolicy.REPLICATE; + case LOCAL_PERSISTENT: + case LOCAL_PERSISTENT_OVERFLOW: + case REPLICATE_PERSISTENT: + case REPLICATE_PERSISTENT_OVERFLOW: + return DataPolicy.PERSISTENT_REPLICATE; + case REPLICATE_PROXY: + return DataPolicy.EMPTY; + default: + return null; + } + + }).orElse(DataPolicy.DEFAULT); + } + + /** + * 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 + */ + private static boolean doSafeIo(IoExceptionThrowingOperation operation) { + + try { + operation.doIo(); + return true; + } + catch (IOException cause) { + return false; + } + } + + /** + * Determines whether the given {@link Region} is a root {@link Region}. + * + * @param region {@link Region} to evaluate. + * @return a boolean value indicating whether the {@link Region} is a root {@link Region}. + * @see org.apache.geode.cache.Region + * @see #isRootRegion(String) + */ + private static boolean isRootRegion(Region region) { + return isRootRegion(region.getFullPath()); + } + + /** + * Determines whether the {@link Region} identified by the given {@link String path} is a root {@link Region}. + * + * @param regionPath {@link String path} identifying the {@link Region} to evaluate. + * @return a boolean value indicating whether the {@link Region} identified by the given {@link String path} + * is a root {@link Region}. + */ + private static boolean isRootRegion(String regionPath) { + return (regionPath.lastIndexOf(Region.SEPARATOR) <= 0); + } + + /** + * Normalizes the given {@link Region#getFullPath() Regon path} by removing all duplicate, repeating + * {@link Region#SEPARATOR} characters between path segments as well as removing the trailing + * {@link Region#SEPARATOR}. + * + * @param regionPath {@link Region#getFullPath()} to normalize. + * @return a normalized version of the given {@link Region#getFullPath()}. + */ + private static String normalizeRegionPath(String regionPath) { + + regionPath = regionPath.replaceAll(REPEATING_REGION_SEPARATOR, Region.SEPARATOR); + + regionPath = regionPath.endsWith(Region.SEPARATOR) + ? regionPath.substring(0, regionPath.length() - 1) : regionPath; + + return regionPath; + } + + /** + * Remembers the given mock {@link GemFireCache} object, which may be a {@link ClientCache} or a peer {@link Cache}. + * + * @param {@link Class sub-type} of the {@link GemFireCache} instance. + * @param mockedGemFireCache {@link GemFireCache} to remember. + * @param useSingletonCache boolean value indicating whether the {@link GemFireCache} is a Singleton. + * @return the given {@link GemFireCache}. + * @throws IllegalArgumentException if {@link GemFireCache} is {@literal null}. + * @see org.apache.geode.cache.GemFireCache + */ + private static T rememberMockedGemFireCache(T mockedGemFireCache, + boolean useSingletonCache) { + + return Optional.ofNullable(mockedGemFireCache) + .map(it -> { + + if (useSingletonCache) { + singletonCache.compareAndSet(null, it); + } + + return it; + }) + .orElseThrow(() -> newIllegalArgumentException("GemFireCache is required")); + } + + /** + * Remembers the given mock {@link Region}. + * + * @param {@link Class type} of the {@link Region} key. + * @param {@link Class type} of the {@link Region} value. + * @param mockRegion {@link Region} to remember. + * @throws IllegalArgumentException if the given {@link Region} is {@literal null}. + * @throws RegionExistsException if the given {@link Region} already exists. + * @return the given {@link Region}. + * @see org.apache.geode.cache.Region + */ + @SuppressWarnings("unchecked") + private static Region rememberMockedRegion(Region mockRegion) { + + String mockRegionPath = Optional.ofNullable(mockRegion).map(Region::getFullPath) + .orElseThrow(() -> newIllegalArgumentException("Region is required")); + + if (regions.putIfAbsent(mockRegionPath, (Region) mockRegion) != null) { + throw new RegionExistsException(mockRegion); + } + + assertThat(regions).containsValue((Region) mockRegion); + + return mockRegion; + } + + /** + * Resolves the single, remembered {@link GemFireCache} if using GemFire in Singleton-mode. + * + * @param {@link Class sub-type} of the {@link GemFireCache} instance. + * @param useSingletonCache boolean value indicating if mock infrastructure is using GemFire Singletons. + * @return an {@link Optional}, single remembered instance of the {@link GemFireCache}. + * @see org.apache.geode.cache.GemFireCache + */ + @SuppressWarnings("unchecked") + private static Optional resolveMockedGemFireCache(boolean useSingletonCache) { + return Optional.ofNullable((T) singletonCache.get()).filter(it -> useSingletonCache); + } + + /** + * Resolves the {@link RegionAttributes} identified by the given {@link String id}. + * + * @param {@link Class type} of the {@link Region} key. + * @param {@link Class type} of the {@link Region} value. + * @param regionAttributesId {@link String id} identifying the {@link RegionAttributes} to resolve. + * @return the resolved {@link RegionAttributes} identified by the given {@link String id}. + * @throws IllegalStateException if {@link RegionAttributes} could not be resolved from the given {@link String id}. + * @see org.apache.geode.cache.RegionAttributes + */ + @SuppressWarnings("unchecked") + private static RegionAttributes resolveRegionAttributes(String regionAttributesId) { + + return (RegionAttributes) Optional.ofNullable(regionAttributes.get(regionAttributesId)).orElseThrow(() -> + newIllegalStateException("RegionAttributes with ID [%s] cannot be found", regionAttributesId)); + } + + /** + * Converts the given {@link String Region name} into a proper {@link Region#getName() Region name}. + * + * @param regionName {@link String Region name} to evaluate. + * @return a proper {@link Region#getName() Region name} from the given {@link String Region name}. + * @throws IllegalArgumentException if {@link String Region name} is {@literal null} + * or {@link String#isEmpty() empty}. + * @see String + */ + private static String toRegionName(String regionName) { + + return Optional.ofNullable(regionName) + .map(String::trim) + .map(it -> { + int lastIndexOfRegionSeparator = it.lastIndexOf(Region.SEPARATOR); + return lastIndexOfRegionSeparator < 0 ? it : it.substring(lastIndexOfRegionSeparator); + }) + .filter(it -> !it.isEmpty()) + .orElseThrow(() -> newIllegalArgumentException("Region name [%s] is required", regionName)); + } + + /** + * Converts the given {@link String Region path} into a proper {@link Region#getFullPath() Region path}. + * + * @param regionPath {@link String Region path} to evaluate. + * @return a proper {@link Region#getFullPath() Region path} from the given {@link String Region path}. + * @throws IllegalArgumentException if {@link String Region path} is {@literal null} + * or {@link String#isEmpty() empty}. + * @see String + */ + private static String toRegionPath(String regionPath) { + + return Optional.ofNullable(regionPath) + .map(String::trim) + .map(it -> it.startsWith(Region.SEPARATOR) ? it : String.format("%1$s%2$s", Region.SEPARATOR, it)) + .map(MockGemFireObjectsSupport::normalizeRegionPath) + .filter(it -> !it.isEmpty()) + .orElseThrow(() -> newIllegalArgumentException("Region path [%s] is required", regionPath)); + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + private static T mockCacheApi(T mockGemFireCache) { + + AtomicBoolean copyOnRead = new AtomicBoolean(false); + + DistributedSystem mockDistributedSystem = mockDistributedSystem(); + + ResourceManager mockResourceManager = mockResourceManager(); + + doAnswer(newSetter(copyOnRead, null)).when(mockGemFireCache).setCopyOnRead(anyBoolean()); + + doAnswer(newSetter(regionAttributes, null)) + .when(mockGemFireCache).setRegionAttributes(anyString(), any(RegionAttributes.class)); + + when(mockGemFireCache.getCopyOnRead()).thenAnswer(newGetter(copyOnRead)); + + when(mockGemFireCache.getDistributedSystem()).thenReturn(mockDistributedSystem); + + when(mockGemFireCache.getRegionAttributes(anyString())) + .thenAnswer(invocation -> regionAttributes.get(invocation.getArgument(0))); + + when(mockGemFireCache.getResourceManager()).thenReturn(mockResourceManager); + + when(mockGemFireCache.createDiskStoreFactory()).thenAnswer(invocation -> mockDiskStoreFactory()); + + when(mockGemFireCache.findDiskStore(anyString())) + .thenAnswer(invocation -> diskStores.get(invocation.getArgument(0))); + + when(mockGemFireCache.listRegionAttributes()).thenReturn(Collections.unmodifiableMap(regionAttributes)); + + doThrow(newUnsupportedOperationException(NOT_SUPPORTED)).when(mockGemFireCache) + .loadCacheXml(any(InputStream.class)); + + return mockRegionServiceApi(mockGemFireCache); + } + + /* (non-Javadoc) */ + private static T mockRegionServiceApi(T mockRegionService) { + + AtomicBoolean closed = new AtomicBoolean(false); + + doAnswer(newSetter(closed, true, null)).when(mockRegionService).close(); + + when(mockRegionService.isClosed()).thenAnswer(newGetter(closed)); + + when(mockRegionService.getCancelCriterion()).thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + + when(mockRegionService.getRegion(anyString())).thenAnswer(invocation -> { + + String regionPath = invocation.getArgument(0); + + String resolvedRegionPath = Optional.ofNullable(regionPath) + .map(String::trim) + .filter(it -> !it.isEmpty()) + .map(MockGemFireObjectsSupport::toRegionPath) + .orElseThrow(() -> newIllegalArgumentException("Region path [%s] is not valid", regionPath)); + + return regions.get(resolvedRegionPath); + }); + + when(mockRegionService.createPdxEnum(anyString(), anyString(), anyInt())) + .thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + + when(mockRegionService.createPdxInstanceFactory(anyString())) + .thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + + when(mockRegionService.rootRegions()).thenAnswer(invocation -> + regions.values().stream().filter(MockGemFireObjectsSupport::isRootRegion).collect(Collectors.toSet())); + + return mockRegionService; + } + + public static ClientCache mockClientCache() { + + ClientCache mockClientCache = mock(ClientCache.class); + + doAnswer(newVoidAnswer(invocation -> mockClientCache.close())).when(mockClientCache).close(anyBoolean()); + + when(mockClientCache.createClientRegionFactory(any(ClientRegionShortcut.class))).thenAnswer(invocation -> + mockClientRegionFactory(mockClientCache, invocation.getArgument(0))); + + when(mockClientCache.createClientRegionFactory(anyString())).thenAnswer(invocation -> + mockClientRegionFactory(mockClientCache, invocation.getArgument(0))); + + return mockQueryService(mockCacheApi(mockClientCache)); + } + + public static GemFireCache mockGemFireCache() { + + GemFireCache mockGemFireCache = mock(GemFireCache.class); + + return mockQueryService(mockCacheApi(mockGemFireCache)); + } + + @SuppressWarnings("unchecked") + public static Cache mockPeerCache() { + + Cache mockCache = mock(Cache.class); + + AtomicInteger lockLease = new AtomicInteger(); + AtomicInteger lockTimeout = new AtomicInteger(); + AtomicInteger messageSyncInterval = new AtomicInteger(); + AtomicInteger searchTimeout = new AtomicInteger(); + + List cacheServers = new ArrayList<>(); + + when(mockCache.addCacheServer()).thenAnswer(invocation -> { + + CacheServer mockCacheServer = mockCacheServer(); + + cacheServers.add(mockCacheServer); + + return mockCacheServer; + }); + + doAnswer(newSetter(lockLease, null)).when(mockCache).setLockLease(anyInt()); + doAnswer(newSetter(lockTimeout, null)).when(mockCache).setLockTimeout(anyInt()); + doAnswer(newSetter(messageSyncInterval, null)).when(mockCache).setMessageSyncInterval(anyInt()); + doAnswer(newSetter(searchTimeout, null)).when(mockCache).setSearchTimeout(anyInt()); + + when(mockCache.isServer()).thenReturn(true); + when(mockCache.getCacheServers()).thenAnswer(invocation -> Collections.unmodifiableList(cacheServers)); + when(mockCache.getLockLease()).thenAnswer(newGetter(lockLease)); + when(mockCache.getLockTimeout()).thenAnswer(newGetter(lockTimeout)); + when(mockCache.getMessageSyncInterval()).thenAnswer(newGetter(messageSyncInterval)); + when(mockCache.getReconnectedCache()).thenAnswer(invocation -> mockPeerCache()); + when(mockCache.getSearchTimeout()).thenAnswer(newGetter(searchTimeout)); + + when(mockCache.createRegionFactory()).thenAnswer(invocation -> mockRegionFactory(mockCache)); + + when(mockCache.createRegionFactory(any(RegionAttributes.class))).thenAnswer(invocation -> + mockRegionFactory(mockCache, invocation.>getArgument(0))); + + when(mockCache.createRegionFactory(any(RegionShortcut.class))).thenAnswer(invocation -> + mockRegionFactory(mockCache, invocation.getArgument(0))); + + when(mockCache.createRegionFactory(anyString())).thenAnswer(invocation -> + mockRegionFactory(mockCache, invocation.getArgument(0))); + + return mockQueryService(mockCacheApi(mockCache)); + } + + public static CacheServer mockCacheServer() { + + CacheServer mockCacheServer = mock(CacheServer.class); + + AtomicBoolean running = new AtomicBoolean(false); + AtomicBoolean tcpNoDelay = new AtomicBoolean(CacheServer.DEFAULT_TCP_NO_DELAY); + + AtomicInteger maxConnections = new AtomicInteger(CacheServer.DEFAULT_MAX_CONNECTIONS); + AtomicInteger maxMessageCount = new AtomicInteger(CacheServer.DEFAULT_MAXIMUM_MESSAGE_COUNT); + AtomicInteger maxThreads = new AtomicInteger(CacheServer.DEFAULT_MAX_THREADS); + AtomicInteger maxTimeBetweenPings = new AtomicInteger(CacheServer.DEFAULT_MAXIMUM_TIME_BETWEEN_PINGS); + AtomicInteger messageTimeToLive = new AtomicInteger(CacheServer.DEFAULT_MESSAGE_TIME_TO_LIVE); + AtomicInteger port = new AtomicInteger(CacheServer.DEFAULT_PORT); + AtomicInteger socketBufferSize = new AtomicInteger(CacheServer.DEFAULT_SOCKET_BUFFER_SIZE); + + AtomicLong loadPollInterval = new AtomicLong(CacheServer.DEFAULT_LOAD_POLL_INTERVAL); + + AtomicReference bindAddress = new AtomicReference<>(CacheServer.DEFAULT_BIND_ADDRESS); + AtomicReference hostnameForClients = new AtomicReference<>(CacheServer.DEFAULT_HOSTNAME_FOR_CLIENTS); + + doAnswer(newSetter(bindAddress, null)) + .when(mockCacheServer).setBindAddress(anyString()); + + doAnswer(newSetter(hostnameForClients, null)) + .when(mockCacheServer).setHostnameForClients(anyString()); + + doAnswer(newSetter(loadPollInterval, null)) + .when(mockCacheServer).setLoadPollInterval(anyLong()); + + doAnswer(newSetter(maxConnections, null)) + .when(mockCacheServer).setMaxConnections(anyInt()); + + doAnswer(newSetter(maxMessageCount, null)) + .when(mockCacheServer).setMaximumMessageCount(anyInt()); + + doAnswer(newSetter(maxThreads, null)) + .when(mockCacheServer).setMaxThreads(anyInt()); + + doAnswer(newSetter(maxTimeBetweenPings, null)) + .when(mockCacheServer).setMaximumTimeBetweenPings(anyInt()); + + doAnswer(newSetter(messageTimeToLive, null)) + .when(mockCacheServer).setMessageTimeToLive(anyInt()); + + doAnswer(newSetter(port, null)) + .when(mockCacheServer).setPort(anyInt()); + + doAnswer(newSetter(socketBufferSize, null)) + .when(mockCacheServer).setSocketBufferSize(anyInt()); + + doAnswer(newSetter(tcpNoDelay, null)) + .when(mockCacheServer).setTcpNoDelay(anyBoolean()); + + when(mockCacheServer.isRunning()).thenAnswer(newGetter(running)); + when(mockCacheServer.getAllClientSessions()).thenReturn(Collections.emptySet()); + when(mockCacheServer.getBindAddress()).thenAnswer(newGetter(bindAddress)); + when(mockCacheServer.getClientSession(any(DistributedMember.class))) + .thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + when(mockCacheServer.getClientSession(anyString())).thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + when(mockCacheServer.getHostnameForClients()).thenAnswer(newGetter(hostnameForClients)); + when(mockCacheServer.getInterestRegistrationListeners()).thenReturn(Collections.emptySet()); + when(mockCacheServer.getLoadPollInterval()).thenAnswer(newGetter(loadPollInterval)); + when(mockCacheServer.getLoadProbe()).thenThrow(newUnsupportedOperationException(NOT_SUPPORTED)); + when(mockCacheServer.getMaxConnections()).thenAnswer(newGetter(maxConnections)); + when(mockCacheServer.getMaximumMessageCount()).thenAnswer(newGetter(maxMessageCount)); + when(mockCacheServer.getMaximumTimeBetweenPings()).thenAnswer(newGetter(maxTimeBetweenPings)); + when(mockCacheServer.getMaxThreads()).thenAnswer(newGetter(maxThreads)); + when(mockCacheServer.getMessageTimeToLive()).thenAnswer(newGetter(messageTimeToLive)); + when(mockCacheServer.getPort()).thenAnswer(newGetter(port)); + when(mockCacheServer.getSocketBufferSize()).thenAnswer(newGetter(socketBufferSize)); + when(mockCacheServer.getTcpNoDelay()).thenAnswer(newGetter(tcpNoDelay)); + + ClientSubscriptionConfig mockClientSubscriptionConfig = mockClientSubscriptionConfig(); + + when(mockCacheServer.getClientSubscriptionConfig()).thenReturn(mockClientSubscriptionConfig); + + doSafeIo(() -> doAnswer(newSetter(running, true, null)).when(mockCacheServer).start()); + doAnswer(newSetter(running, false, null)).when(mockCacheServer).stop(); + + return mockCacheServer; + } + + public static ClientRegionFactory mockClientRegionFactory(ClientCache mockClientCache, + ClientRegionShortcut clientRegionShortcut) { + + return mockClientRegionFactory(mockClientCache, clientRegionShortcut, null); + } + + public static ClientRegionFactory mockClientRegionFactory(ClientCache mockClientCache, + String regionAttributesId) { + + return mockClientRegionFactory(mockClientCache, null, + resolveRegionAttributes(regionAttributesId)); + } + + @SuppressWarnings("unchecked") + public static ClientRegionFactory mockClientRegionFactory(ClientCache mockClientCache, + ClientRegionShortcut clientRegionShortcut, RegionAttributes regionAttributes) { + + ClientRegionFactory mockClientRegionFactory = + mock(ClientRegionFactory.class, mockObjectIdentifier("MockClientRegionFactory")); + + ExpirationAttributes DEFAULT_EXPIRATION_ATTRIBUTES = + new ExpirationAttributes(0, ExpirationAction.INVALIDATE); + + Optional> optionalRegionAttributes = Optional.ofNullable(regionAttributes); + + AtomicBoolean cloningEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getCloningEnabled).orElse(false)); + + AtomicBoolean concurrencyChecksEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getConcurrencyChecksEnabled).orElse(false)); + + AtomicBoolean diskSynchronous = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::isDiskSynchronous).orElse(true)); + + AtomicBoolean statisticsEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getStatisticsEnabled).orElse(false)); + + AtomicInteger concurrencyLevel = new AtomicInteger(optionalRegionAttributes + .map(RegionAttributes::getConcurrencyLevel).orElse(16)); + + AtomicInteger initialCapacity = new AtomicInteger(optionalRegionAttributes + .map(RegionAttributes::getInitialCapacity).orElse(16)); + + AtomicReference compressor = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCompressor).orElse(null)); + + AtomicReference> customEntryIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCustomEntryIdleTimeout).orElse(null)); + + AtomicReference> customEntryTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCustomEntryTimeToLive).orElse(null)); + + AtomicReference dataPolicy = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getDataPolicy).orElseGet(() -> convert(clientRegionShortcut))); + + AtomicReference diskStoreName = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getDiskStoreName).orElse(null)); + + AtomicReference entryIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEntryIdleTimeout).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference entryTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEntryTimeToLive).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference evictionAttributes = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEvictionAttributes).orElseGet(() -> EvictionAttributes.createLRUEntryAttributes())); + + AtomicReference> keyConstraint = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getKeyConstraint).orElse(null)); + + AtomicReference loadFactor = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getLoadFactor).orElse(0.75f)); + + AtomicReference poolName = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getPoolName).orElse(null)); + + AtomicReference regionIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getRegionIdleTimeout).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference regionTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getRegionTimeToLive).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference> valueConstraint = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getValueConstraint).orElse(null)); + + List cacheListeners = new ArrayList<>(Arrays.asList(nullSafeArray(optionalRegionAttributes + .map(RegionAttributes::getCacheListeners).orElse(null), CacheListener.class))); + + when(mockClientRegionFactory.addCacheListener(any(CacheListener.class))) + .thenAnswer(newAdder(cacheListeners, mockClientRegionFactory)); + + when(mockClientRegionFactory.initCacheListeners(any(CacheListener[].class))).thenAnswer(invocation -> { + cacheListeners.clear(); + Collections.addAll(cacheListeners, invocation.getArgument(0)); + return mockClientRegionFactory; + }); + + when(mockClientRegionFactory.setCloningEnabled(anyBoolean())) + .thenAnswer(newSetter(cloningEnabled, mockClientRegionFactory)); + + when(mockClientRegionFactory.setCompressor(any(Compressor.class))) + .thenAnswer(newSetter(compressor, mockClientRegionFactory)); + + doAnswer(newSetter(concurrencyChecksEnabled, mockClientRegionFactory)) + .when(mockClientRegionFactory).setConcurrencyChecksEnabled(anyBoolean()); + + when(mockClientRegionFactory.setConcurrencyLevel(anyInt())) + .thenAnswer(newSetter(concurrencyLevel, mockClientRegionFactory)); + + when(mockClientRegionFactory.setCustomEntryIdleTimeout(any(CustomExpiry.class))) + .thenAnswer(newSetter(customEntryIdleTimeout, mockClientRegionFactory)); + + when(mockClientRegionFactory.setCustomEntryTimeToLive(any(CustomExpiry.class))) + .thenAnswer(newSetter(customEntryTimeToLive, mockClientRegionFactory)); + + when(mockClientRegionFactory.setDiskStoreName(anyString())) + .thenAnswer(newSetter(diskStoreName, mockClientRegionFactory)); + + when(mockClientRegionFactory.setDiskSynchronous(anyBoolean())) + .thenAnswer(newSetter(diskSynchronous, mockClientRegionFactory)); + + when(mockClientRegionFactory.setEntryIdleTimeout(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(entryIdleTimeout, mockClientRegionFactory)); + + when(mockClientRegionFactory.setEntryTimeToLive(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(entryTimeToLive, mockClientRegionFactory)); + + when(mockClientRegionFactory.setEvictionAttributes(any(EvictionAttributes.class))) + .thenAnswer(newSetter(evictionAttributes, mockClientRegionFactory)); + + when(mockClientRegionFactory.setInitialCapacity(anyInt())) + .thenAnswer(newSetter(initialCapacity, mockClientRegionFactory)); + + when(mockClientRegionFactory.setKeyConstraint(any(Class.class))) + .thenAnswer(newSetter(keyConstraint, mockClientRegionFactory)); + + when(mockClientRegionFactory.setLoadFactor(anyFloat())) + .thenAnswer(newSetter(loadFactor, mockClientRegionFactory)); + + when(mockClientRegionFactory.setPoolName(anyString())) + .thenAnswer(newSetter(poolName, mockClientRegionFactory)); + + when(mockClientRegionFactory.setRegionIdleTimeout(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(regionIdleTimeout, mockClientRegionFactory)); + + when(mockClientRegionFactory.setRegionTimeToLive(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(regionTimeToLive, mockClientRegionFactory)); + + when(mockClientRegionFactory.setStatisticsEnabled(anyBoolean())) + .thenAnswer(newSetter(statisticsEnabled, mockClientRegionFactory)); + + when(mockClientRegionFactory.setValueConstraint(any(Class.class))) + .thenAnswer(newSetter(valueConstraint, mockClientRegionFactory)); + + RegionAttributes mockRegionAttributes = + mock(RegionAttributes.class, mockObjectIdentifier("MockRegionAttributes")); + + when(mockRegionAttributes.getCacheListeners()) + .thenAnswer(newGetter(() -> cacheListeners.toArray(new CacheListener[cacheListeners.size()]))); + + when(mockRegionAttributes.getCloningEnabled()).thenAnswer(newGetter(cloningEnabled)); + when(mockRegionAttributes.getCompressor()).thenAnswer(newGetter(compressor)); + when(mockRegionAttributes.getConcurrencyChecksEnabled()).thenAnswer(newGetter(concurrencyChecksEnabled)); + when(mockRegionAttributes.getConcurrencyLevel()).thenAnswer(newGetter(concurrencyLevel)); + when(mockRegionAttributes.getCustomEntryIdleTimeout()).thenAnswer(newGetter(customEntryIdleTimeout)); + when(mockRegionAttributes.getCustomEntryTimeToLive()).thenAnswer(newGetter(customEntryTimeToLive)); + when(mockRegionAttributes.getDataPolicy()).thenAnswer(newGetter(dataPolicy)); + when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName)); + when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous)); + when(mockRegionAttributes.getEntryIdleTimeout()).thenAnswer(newGetter(entryIdleTimeout)); + when(mockRegionAttributes.getEntryTimeToLive()).thenAnswer(newGetter(entryTimeToLive)); + when(mockRegionAttributes.getEvictionAttributes()).thenAnswer(newGetter(evictionAttributes)); + when(mockRegionAttributes.getInitialCapacity()).thenAnswer(newGetter(initialCapacity)); + when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint)); + when(mockRegionAttributes.getLoadFactor()).thenAnswer(newGetter(loadFactor)); + when(mockRegionAttributes.getPoolName()).thenAnswer(newGetter(poolName)); + when(mockRegionAttributes.getRegionIdleTimeout()).thenAnswer(newGetter(regionIdleTimeout)); + when(mockRegionAttributes.getRegionTimeToLive()).thenAnswer(newGetter(regionTimeToLive)); + when(mockRegionAttributes.getStatisticsEnabled()).thenAnswer(newGetter(statisticsEnabled)); + when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint)); + + when(mockClientRegionFactory.create(anyString())).thenAnswer(invocation -> + mockRegion(mockClientCache, invocation.getArgument(0), mockRegionAttributes)); + + when(mockClientRegionFactory.createSubregion(any(Region.class), anyString())).thenAnswer(invocation -> + mockSubRegion(invocation.getArgument(0), invocation.getArgument(1), mockRegionAttributes)); + + return mockClientRegionFactory; + } + + public static ClientSubscriptionConfig mockClientSubscriptionConfig() { + + ClientSubscriptionConfig mockClientSubscriptionConfig = mock(ClientSubscriptionConfig.class); + + AtomicInteger subscriptionCapacity = new AtomicInteger(ClientSubscriptionConfig.DEFAULT_CAPACITY); + + AtomicReference subscriptionDiskStoreName = new AtomicReference<>(""); + + AtomicReference subscriptionEvictionPolicy = + new AtomicReference<>(SubscriptionEvictionPolicy.DEFAULT); + + Function stringToSubscriptionEvictionPolicyConverter = + arg -> SubscriptionEvictionPolicy.valueOfIgnoreCase(String.valueOf(arg)); + + Function subscriptionEvictionPolicyToStringConverter = + arg -> Optional.ofNullable(arg).map(Object::toString).map(String::toLowerCase).orElse(null); + + doAnswer(newSetter(subscriptionCapacity, null)) + .when(mockClientSubscriptionConfig).setCapacity(anyInt()); + + doAnswer(newSetter(subscriptionDiskStoreName, null)) + .when(mockClientSubscriptionConfig).setDiskStoreName(anyString()); + + doAnswer(newSetter(subscriptionEvictionPolicy, stringToSubscriptionEvictionPolicyConverter, null)) + .when(mockClientSubscriptionConfig).setEvictionPolicy(anyString()); + + when(mockClientSubscriptionConfig.getCapacity()).thenAnswer(newGetter(subscriptionCapacity)); + when(mockClientSubscriptionConfig.getDiskStoreName()).thenAnswer(newGetter(subscriptionDiskStoreName)); + when(mockClientSubscriptionConfig.getEvictionPolicy()).thenAnswer(newGetter(subscriptionEvictionPolicy, + subscriptionEvictionPolicyToStringConverter)); + + return mockClientSubscriptionConfig; + } + + public static DiskStoreFactory mockDiskStoreFactory() { + + DiskStoreFactory mockDiskStoreFactory = mock(DiskStoreFactory.class); + + AtomicBoolean allowForceCompaction = new AtomicBoolean(DiskStoreFactory.DEFAULT_ALLOW_FORCE_COMPACTION); + AtomicBoolean autoCompact = new AtomicBoolean(DiskStoreFactory.DEFAULT_AUTO_COMPACT); + + AtomicInteger compactionThreshold = new AtomicInteger(DiskStoreFactory.DEFAULT_COMPACTION_THRESHOLD); + AtomicInteger queueSize = new AtomicInteger(DiskStoreFactory.DEFAULT_QUEUE_SIZE); + AtomicInteger writeBufferSize = new AtomicInteger(DiskStoreFactory.DEFAULT_WRITE_BUFFER_SIZE); + + AtomicLong maxOplogSize = new AtomicLong(DiskStoreFactory.DEFAULT_MAX_OPLOG_SIZE); + AtomicLong timeInterval = new AtomicLong(DiskStoreFactory.DEFAULT_TIME_INTERVAL); + + AtomicReference diskDirectories = + new AtomicReference<>(new File[] { FileSystemUtils.WORKING_DIRECTORY }); + + AtomicReference diskDiretorySizes = new AtomicReference<>(new int[0]); + + AtomicReference diskUsageCriticalPercentage = + new AtomicReference<>(DiskStoreFactory.DEFAULT_DISK_USAGE_CRITICAL_PERCENTAGE); + + AtomicReference diskUsageWarningPercentage = + new AtomicReference<>(DiskStoreFactory.DEFAULT_DISK_USAGE_WARNING_PERCENTAGE); + + when(mockDiskStoreFactory.setAllowForceCompaction(anyBoolean())) + .thenAnswer(newSetter(allowForceCompaction, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setAutoCompact(anyBoolean())) + .thenAnswer(newSetter(autoCompact, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setCompactionThreshold(anyInt())) + .thenAnswer(newSetter(compactionThreshold, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setDiskDirs(any(File[].class))).thenAnswer(invocation -> { + + File[] resolveDiskDirectories = nullSafeArray(invocation.getArgument(0), File.class); + + int[] resolvedDiskDirectorySizes = new int[resolveDiskDirectories.length]; + + Arrays.fill(resolvedDiskDirectorySizes, DiskStoreFactory.DEFAULT_DISK_DIR_SIZE); + + diskDirectories.set(resolveDiskDirectories); + diskDiretorySizes.set(resolvedDiskDirectorySizes); + + return mockDiskStoreFactory; + }); + + when(mockDiskStoreFactory.setDiskDirsAndSizes(any(File[].class), any(int[].class))).thenAnswer(invocation -> { + + diskDirectories.set(invocation.getArgument(0)); + diskDiretorySizes.set(invocation.getArgument(1)); + + return mockDiskStoreFactory; + }); + + when(mockDiskStoreFactory.setDiskUsageCriticalPercentage(anyFloat())) + .thenAnswer(newSetter(diskUsageCriticalPercentage, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setDiskUsageWarningPercentage(anyFloat())) + .thenAnswer(newSetter(diskUsageWarningPercentage, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setMaxOplogSize(anyLong())) + .thenAnswer(newSetter(maxOplogSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setQueueSize(anyInt())) + .thenAnswer(newSetter(queueSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setTimeInterval(anyLong())) + .thenAnswer(newSetter(timeInterval, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.setWriteBufferSize(anyInt())) + .thenAnswer(newSetter(writeBufferSize, mockDiskStoreFactory)); + + when(mockDiskStoreFactory.create(anyString())).thenAnswer(invocation -> { + + String name = invocation.getArgument(0); + + DiskStore mockDiskStore = mock(DiskStore.class, name); + + when(mockDiskStore.getAllowForceCompaction()).thenReturn(allowForceCompaction.get()); + when(mockDiskStore.getAutoCompact()).thenReturn(autoCompact.get()); + when(mockDiskStore.getCompactionThreshold()).thenReturn(compactionThreshold.get()); + when(mockDiskStore.getDiskDirs()).thenReturn(diskDirectories.get()); + when(mockDiskStore.getDiskDirSizes()).thenReturn(diskDiretorySizes.get()); + when(mockDiskStore.getDiskUsageCriticalPercentage()).thenReturn(diskUsageCriticalPercentage.get()); + when(mockDiskStore.getDiskUsageWarningPercentage()).thenReturn(diskUsageWarningPercentage.get()); + when(mockDiskStore.getDiskStoreUUID()).thenReturn(UUID.randomUUID()); + when(mockDiskStore.getMaxOplogSize()).thenReturn(maxOplogSize.get()); + when(mockDiskStore.getName()).thenReturn(name); + when(mockDiskStore.getQueueSize()).thenReturn(queueSize.get()); + when(mockDiskStore.getTimeInterval()).thenReturn(timeInterval.get()); + when(mockDiskStore.getWriteBufferSize()).thenReturn(writeBufferSize.get()); + + diskStores.put(name, mockDiskStore); + + return mockDiskStore; + }); + + return mockDiskStoreFactory; + } + + public static DistributedSystem mockDistributedSystem() { + + DistributedSystem mockDistributedSystem = mock(DistributedSystem.class); + + when(mockDistributedSystem.getProperties()).thenReturn(new Properties()); + when(mockDistributedSystem.getReconnectedSystem()).thenAnswer(invocation -> mockDistributedSystem()); + + return mockDistributedSystem; + } + + public static PoolFactory mockPoolFactory() { + + PoolFactory mockPoolFactory = mock(PoolFactory.class); + + AtomicBoolean multiuserAuthentication = new AtomicBoolean(PoolFactory.DEFAULT_MULTIUSER_AUTHENTICATION); + AtomicBoolean prSingleHopEnabled = new AtomicBoolean(PoolFactory.DEFAULT_PR_SINGLE_HOP_ENABLED); + AtomicBoolean subscriptionEnabled = new AtomicBoolean(PoolFactory.DEFAULT_SUBSCRIPTION_ENABLED); + AtomicBoolean threadLocalConnections = new AtomicBoolean(PoolFactory.DEFAULT_THREAD_LOCAL_CONNECTIONS); + + AtomicInteger freeConnectionTimeout = new AtomicInteger(PoolFactory.DEFAULT_FREE_CONNECTION_TIMEOUT); + AtomicInteger loadConditioningInterval = new AtomicInteger(PoolFactory.DEFAULT_LOAD_CONDITIONING_INTERVAL); + AtomicInteger maxConnections = new AtomicInteger(PoolFactory.DEFAULT_MAX_CONNECTIONS); + AtomicInteger minConnections = new AtomicInteger(PoolFactory.DEFAULT_MIN_CONNECTIONS); + AtomicInteger readTimeout = new AtomicInteger(PoolFactory.DEFAULT_READ_TIMEOUT); + AtomicInteger retryAttempts = new AtomicInteger(PoolFactory.DEFAULT_RETRY_ATTEMPTS); + AtomicInteger socketBufferSize = new AtomicInteger(PoolFactory.DEFAULT_SOCKET_BUFFER_SIZE); + AtomicInteger statisticInterval = new AtomicInteger(PoolFactory.DEFAULT_STATISTIC_INTERVAL); + AtomicInteger subscriptionAckInterval = new AtomicInteger(PoolFactory.DEFAULT_SUBSCRIPTION_ACK_INTERVAL); + AtomicInteger subscriptionMessageTrackingTimeout = new AtomicInteger(PoolFactory.DEFAULT_SUBSCRIPTION_MESSAGE_TRACKING_TIMEOUT); + AtomicInteger subscriptionRedundancy = new AtomicInteger(PoolFactory.DEFAULT_SUBSCRIPTION_REDUNDANCY); + + AtomicLong idleTimeout = new AtomicLong(PoolFactory.DEFAULT_IDLE_TIMEOUT); + AtomicLong pingInterval = new AtomicLong(PoolFactory.DEFAULT_PING_INTERVAL); + + AtomicReference serverGroup = new AtomicReference<>(PoolFactory.DEFAULT_SERVER_GROUP); + + List locators = new ArrayList<>(); + List servers = new ArrayList<>(); + + when(mockPoolFactory.addLocator(anyString(), anyInt())).thenAnswer(invocation -> { + locators.add(new InetSocketAddress(invocation.getArgument(0), invocation.getArgument(1))); + return mockPoolFactory; + }); + + when(mockPoolFactory.addServer(anyString(), anyInt())).thenAnswer(invocation -> { + servers.add(new InetSocketAddress(invocation.getArgument(0), invocation.getArgument(1))); + return mockPoolFactory; + }); + + when(mockPoolFactory.setFreeConnectionTimeout(anyInt())) + .thenAnswer(newSetter(freeConnectionTimeout, mockPoolFactory)); + + when(mockPoolFactory.setIdleTimeout(anyLong())) + .thenAnswer(newSetter(idleTimeout, mockPoolFactory)); + + when(mockPoolFactory.setLoadConditioningInterval(anyInt())) + .thenAnswer(newSetter(loadConditioningInterval, mockPoolFactory)); + + when(mockPoolFactory.setMaxConnections(anyInt())) + .thenAnswer(newSetter(maxConnections, mockPoolFactory)); + + when(mockPoolFactory.setMinConnections(anyInt())) + .thenAnswer(newSetter(minConnections, mockPoolFactory)); + + when(mockPoolFactory.setMultiuserAuthentication(anyBoolean())) + .thenAnswer(newSetter(multiuserAuthentication, mockPoolFactory)); + + when(mockPoolFactory.setPingInterval(anyLong())) + .thenAnswer(newSetter(pingInterval, mockPoolFactory)); + + when(mockPoolFactory.setPRSingleHopEnabled(anyBoolean())) + .thenAnswer(newSetter(prSingleHopEnabled, mockPoolFactory)); + + when(mockPoolFactory.setReadTimeout(anyInt())) + .thenAnswer(newSetter(readTimeout, mockPoolFactory)); + + when(mockPoolFactory.setRetryAttempts(anyInt())) + .thenAnswer(newSetter(retryAttempts, mockPoolFactory)); + + when(mockPoolFactory.setServerGroup(anyString())) + .thenAnswer(newSetter(serverGroup, mockPoolFactory)); + + when(mockPoolFactory.setSocketBufferSize(anyInt())) + .thenAnswer(newSetter(socketBufferSize, mockPoolFactory)); + + when(mockPoolFactory.setStatisticInterval(anyInt())) + .thenAnswer(newSetter(statisticInterval, mockPoolFactory)); + + when(mockPoolFactory.setSubscriptionAckInterval(anyInt())) + .thenAnswer(newSetter(subscriptionAckInterval, mockPoolFactory)); + + when(mockPoolFactory.setSubscriptionEnabled(anyBoolean())) + .thenAnswer(newSetter(subscriptionEnabled, mockPoolFactory)); + + when(mockPoolFactory.setSubscriptionMessageTrackingTimeout(anyInt())) + .thenAnswer(newSetter(subscriptionMessageTrackingTimeout, mockPoolFactory)); + + when(mockPoolFactory.setSubscriptionRedundancy(anyInt())) + .thenAnswer(newSetter(subscriptionRedundancy, mockPoolFactory)); + + when(mockPoolFactory.setThreadLocalConnections(anyBoolean())) + .thenAnswer(newSetter(threadLocalConnections, mockPoolFactory)); + + when(mockPoolFactory.create(anyString())).thenAnswer(invocation -> { + + String name = invocation.getArgument(0); + + Pool mockPool = mock(Pool.class, name); + + AtomicBoolean destroyed = new AtomicBoolean(false); + + doAnswer(invocationOnMock -> { + destroyed.set(true); + return null; + }).when(mockPool).destroy(); + + doAnswer(invocationOnMock -> { + destroyed.set(true); + return null; + }).when(mockPool).destroy(anyBoolean()); + + when(mockPool.isDestroyed()).thenAnswer(newGetter(destroyed)); + when(mockPool.getFreeConnectionTimeout()).thenReturn(freeConnectionTimeout.get()); + when(mockPool.getIdleTimeout()).thenReturn(idleTimeout.get()); + when(mockPool.getLoadConditioningInterval()).thenReturn(loadConditioningInterval.get()); + when(mockPool.getLocators()).thenReturn(locators); + when(mockPool.getMaxConnections()).thenReturn(maxConnections.get()); + when(mockPool.getMinConnections()).thenReturn(minConnections.get()); + when(mockPool.getMultiuserAuthentication()).thenReturn(multiuserAuthentication.get()); + when(mockPool.getName()).thenReturn(name); + when(mockPool.getPingInterval()).thenReturn(pingInterval.get()); + when(mockPool.getPRSingleHopEnabled()).thenReturn(prSingleHopEnabled.get()); + when(mockPool.getReadTimeout()).thenReturn(readTimeout.get()); + when(mockPool.getRetryAttempts()).thenReturn(retryAttempts.get()); + when(mockPool.getServerGroup()).thenReturn(serverGroup.get()); + when(mockPool.getServers()).thenReturn(servers); + when(mockPool.getSocketBufferSize()).thenReturn(socketBufferSize.get()); + when(mockPool.getStatisticInterval()).thenReturn(statisticInterval.get()); + when(mockPool.getSubscriptionAckInterval()).thenReturn(subscriptionAckInterval.get()); + when(mockPool.getSubscriptionEnabled()).thenReturn(subscriptionEnabled.get()); + when(mockPool.getSubscriptionMessageTrackingTimeout()).thenReturn(subscriptionMessageTrackingTimeout.get()); + when(mockPool.getSubscriptionRedundancy()).thenReturn(subscriptionRedundancy.get()); + when(mockPool.getThreadLocalConnections()).thenReturn(threadLocalConnections.get()); + + return mockPool; + }); + + return mockPoolFactory; + } + + public static Pool mockQueryService(Pool pool) { + + QueryService mockQueryService = mockQueryService(); + + when(pool.getQueryService()).thenReturn(mockQueryService); + + return pool; + } + + public static T mockQueryService(T regionService) { + + QueryService mockQueryService = mockQueryService(); + + when(regionService.getQueryService()).thenReturn(mockQueryService); + + if (regionService instanceof ClientCache) { + when(((ClientCache) regionService).getLocalQueryService()).thenReturn(mockQueryService); + } + + return regionService; + } + + // TODO write more mocking logic for the QueryService interface + public static QueryService mockQueryService() { + + QueryService mockQueryService = mock(QueryService.class); + + Set cqQueries = new ConcurrentHashSet<>(); + + try { + when(mockQueryService.getCqs()).thenAnswer(invocation -> cqQueries.toArray(new CqQuery[cqQueries.size()])); + + when(mockQueryService.getCq(anyString())).thenAnswer(invocation -> + cqQueries.stream().filter(cqQuery -> invocation.getArgument(0).equals(cqQuery.getName())) + .findFirst().orElse(null)); + + when(mockQueryService.getCqs(anyString())).thenAnswer(invocation -> { + + List cqQueriesByRegion = cqQueries.stream().filter(cqQuery -> { + + String queryString = cqQuery.getQueryString(); + + int indexOfFromClause = queryString.indexOf(FROM_KEYWORD); + int indexOfWhereClause = queryString.indexOf(WHERE_KEYWORD); + + queryString = (indexOfFromClause > -1 + ? queryString.substring(indexOfFromClause + FROM_KEYWORD.length()) : queryString); + + queryString = (indexOfWhereClause > 0 ? queryString.substring(0, indexOfWhereClause) : queryString); + + queryString = (queryString.startsWith(Region.SEPARATOR) ? queryString.substring(1) : queryString); + + return invocation.getArgument(0).equals(queryString.trim()); + + }).collect(Collectors.toList()); + + return cqQueriesByRegion.toArray(new CqQuery[cqQueriesByRegion.size()]); + }); + + when(mockQueryService.newCq(anyString(), any(CqAttributes.class))).thenAnswer(invocation -> + add(cqQueries, mockCqQuery(null, invocation.getArgument(0), invocation.getArgument(1), + false))); + + when(mockQueryService.newCq(anyString(), any(CqAttributes.class), anyBoolean())).thenAnswer(invocation -> + add(cqQueries, mockCqQuery(null, invocation.getArgument(0), invocation.getArgument(1), + invocation.getArgument(2)))); + + when(mockQueryService.newCq(anyString(), anyString(), any(CqAttributes.class))).thenAnswer(invocation -> + add(cqQueries, mockCqQuery(invocation.getArgument(0), invocation.getArgument(1), + invocation.getArgument(2), false))); + + when(mockQueryService.newCq(anyString(), anyString(), any(CqAttributes.class), anyBoolean())) + .thenAnswer(invocation -> add(cqQueries, mockCqQuery(invocation.getArgument(0), + invocation.getArgument(1), invocation.getArgument(2), invocation.getArgument(3)))); + } + catch (Exception cause) { + throw new MockObjectInvocationException(cause); + } + + return mockQueryService; + } + + private static CqQuery add(Collection cqQueries, CqQuery cqQuery) { + + cqQueries.add(cqQuery); + + return cqQuery; + } + + private static CqQuery mockCqQuery(String name, String queryString, CqAttributes cqAttributes, boolean durable) { + + CqQuery mockCqQuery = mock(CqQuery.class); + + Query mockQuery = mockQuery(queryString); + + AtomicBoolean closed = new AtomicBoolean(false); + AtomicBoolean running = new AtomicBoolean(false); + AtomicBoolean stopped = new AtomicBoolean(true); + + when(mockCqQuery.getCqAttributes()).thenReturn(cqAttributes); + when(mockCqQuery.getName()).thenReturn(name); + when(mockCqQuery.getQuery()).thenReturn(mockQuery); + when(mockCqQuery.getQueryString()).thenReturn(queryString); + + try { + doAnswer(newSetter(closed, true, null)).when(mockCqQuery).close(); + + doAnswer(invocation -> { + + running.set(true); + stopped.set(false); + + return null; + + }).when(mockCqQuery).execute(); + + doAnswer(invocation -> { + + running.set(false); + stopped.set(true); + + return null; + + }).when(mockCqQuery).stop(); + } + catch (Exception cause) { + throw new MockObjectInvocationException(cause); + } + + when(mockCqQuery.isClosed()).thenAnswer(newGetter(closed)); + when(mockCqQuery.isDurable()).thenReturn(durable); + when(mockCqQuery.isRunning()).thenAnswer(newGetter(running)); + when(mockCqQuery.isStopped()).thenAnswer(newGetter(stopped)); + + return mockCqQuery; + } + + private static Query mockQuery(String queryString) { + + Query mockQuery = mock(Query.class); + + QueryStatistics mockQueryStatistics = mockQueryStatistics(mockQuery); + + when(mockQuery.getQueryString()).thenReturn(queryString); + when(mockQuery.getStatistics()).thenReturn(mockQueryStatistics); + + return mockQuery; + } + + private static QueryStatistics mockQueryStatistics(Query query) { + + QueryStatistics mockQueryStatistics = mock(QueryStatistics.class); + + AtomicLong numberOfExecutions = new AtomicLong(0L); + + Answer executeAnswer = invocation -> { + numberOfExecutions.incrementAndGet(); + return null; + }; + + try { + when(query.execute()).thenAnswer(executeAnswer); + when(query.execute(any(Object[].class))).thenAnswer(executeAnswer); + when(query.execute(any(RegionFunctionContext.class))).thenAnswer(executeAnswer); + when(query.execute(any(RegionFunctionContext.class), any(Object[].class))).thenAnswer(executeAnswer); + } + catch (Exception cause) { + throw new MockObjectInvocationException(cause); + } + + when(mockQueryStatistics.getNumExecutions()).thenAnswer(newGetter(numberOfExecutions)); + when(mockQueryStatistics.getTotalExecutionTime()).thenReturn(0L); + + return mockQueryStatistics; + } + + @SuppressWarnings("unchecked") + public static Region mockRegion(RegionService regionService, String name, + RegionAttributes regionAttributes) { + + Map data = new ConcurrentHashMap<>(); + + Region mockRegion = mock(Region.class, name); + + Set> subRegions = new CopyOnWriteArraySet<>(); + + when(mockRegion.getAttributes()).thenReturn(regionAttributes); + when(mockRegion.getFullPath()).thenReturn(toRegionPath(name)); + when(mockRegion.getName()).thenReturn(toRegionName(name)); + when(mockRegion.getRegionService()).thenReturn(regionService); + + when(mockRegion.getSubregion(anyString())).thenAnswer(invocation -> { + + String subRegionPath = toRegionPath(invocation.getArgument(0)); + String subRegionFullPath = String.format("%1$s%2$s", mockRegion.getFullPath(), subRegionPath); + + return regions.get(subRegionFullPath); + }); + + when(mockRegion.get(ArgumentMatchers.any())).thenAnswer(invocation -> + data.get(invocation.getArgument(0))); + + when(mockRegion.getEntry(ArgumentMatchers.any())).thenAnswer(invocation -> + data.entrySet().stream().filter(entry -> entry.getKey().equals(invocation.getArgument(0))).findFirst()); + + when(mockRegion.put(any(), any())).thenAnswer(invocation -> + data.put(invocation.getArgument(0), invocation.getArgument(1))); + + when(mockRegion.size()).thenAnswer(invocation -> data.size()); + + when(mockRegion.subregions(anyBoolean())).thenAnswer(invocation -> { + + boolean recursive = invocation.getArgument(0); + + return recursive + ? subRegions.stream() + .flatMap(subRegion -> subRegion.subregions(true).stream()) + .collect(Collectors.toSet()) + : subRegions; + }); + + return rememberMockedRegion(mockRegion); + } + + public static Region mockSubRegion(Region parent, String name, + RegionAttributes regionAttributes) { + + String subRegionName = String.format("%1$s%2$s", parent.getFullPath(), toRegionPath(name)); + + Region mockSubRegion = mockRegion(parent.getRegionService(), subRegionName, regionAttributes); + + parent.subregions(false).add(mockSubRegion); + + return mockSubRegion; + } + + public static RegionFactory mockRegionFactory(Cache mockCache) { + return mockRegionFactory(mockCache, null, null); + } + + public static RegionFactory mockRegionFactory(Cache mockCache, + RegionAttributes regionAttributes) { + + return mockRegionFactory(mockCache, null, regionAttributes); + } + + public static RegionFactory mockRegionFactory(Cache mockCache, RegionShortcut regionShortcut) { + return mockRegionFactory(mockCache, regionShortcut, null); + } + + public static RegionFactory mockRegionFactory(Cache mockCache, String regionAttributesId) { + return mockRegionFactory(mockCache, null, resolveRegionAttributes(regionAttributesId)); + } + + @SuppressWarnings("unchecked") + public static RegionFactory mockRegionFactory(Cache mockCache, RegionShortcut regionShortcut, + RegionAttributes regionAttributes) { + + RegionFactory mockRegionFactory = mock(RegionFactory.class, + mockObjectIdentifier("MockRegionFactory")); + + Optional> optionalRegionAttributes = Optional.ofNullable(regionAttributes); + + ExpirationAttributes DEFAULT_EXPIRATION_ATTRIBUTES = + new ExpirationAttributes(0, ExpirationAction.INVALIDATE); + + AtomicBoolean cloningEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getCloningEnabled).orElse(false)); + + AtomicBoolean concurrencyChecksEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getConcurrencyChecksEnabled).orElse(true)); + + AtomicBoolean diskSynchronous = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::isDiskSynchronous).orElse(true)); + + AtomicBoolean enableAsyncConflation = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getEnableAsyncConflation).orElse(false)); + + AtomicBoolean enableSubscriptionConflation = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getEnableSubscriptionConflation).orElse(false)); + + AtomicBoolean ignoreJta = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getIgnoreJTA).orElse(false)); + + AtomicBoolean indexMaintenanceSynchronous = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getIndexMaintenanceSynchronous).orElse(true)); + + AtomicBoolean lockGrantor = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::isLockGrantor).orElse(false)); + + AtomicBoolean multicastEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getMulticastEnabled).orElse(false)); + + AtomicBoolean offHeap = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getOffHeap).orElse(false)); + + AtomicBoolean statisticsEnabled = new AtomicBoolean(optionalRegionAttributes + .map(RegionAttributes::getStatisticsEnabled).orElse(false)); + + AtomicInteger concurrencyLevel = new AtomicInteger(optionalRegionAttributes + .map(RegionAttributes::getConcurrencyLevel).orElse(16)); + + AtomicInteger initialCapacity = new AtomicInteger(optionalRegionAttributes + .map(RegionAttributes::getInitialCapacity).orElse(16)); + + AtomicReference cacheLoader = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCacheLoader).orElse(null)); + + AtomicReference cacheWriter = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCacheWriter).orElse(null)); + + AtomicReference compressor = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCompressor).orElse(null)); + + AtomicReference> customEntryIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCustomEntryIdleTimeout).orElse(null)); + + AtomicReference> customEntryTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getCustomEntryTimeToLive).orElse(null)); + + AtomicReference dataPolicy = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getDataPolicy).orElseGet(() -> convert(regionShortcut))); + + AtomicReference diskStoreName = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getDiskStoreName).orElse(null)); + + AtomicReference entryIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEntryIdleTimeout).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference entryTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEntryTimeToLive).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference evictionAttributes = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getEvictionAttributes).orElseGet(() -> EvictionAttributes.createLRUEntryAttributes())); + + AtomicReference> keyConstraint = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getKeyConstraint).orElse(null)); + + AtomicReference loadFactor = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getLoadFactor).orElse(0.75f)); + + AtomicReference> partitionAttributes = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getPartitionAttributes).orElse(null)); + + AtomicReference poolName = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getPoolName).orElse(null)); + + AtomicReference regionIdleTimeout = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getRegionIdleTimeout).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference regionTimeToLive = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getRegionTimeToLive).orElse(DEFAULT_EXPIRATION_ATTRIBUTES)); + + AtomicReference scope = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getScope).orElse(Scope.DISTRIBUTED_NO_ACK)); + + AtomicReference subscriptionAttributes = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getSubscriptionAttributes).orElseGet(() -> new SubscriptionAttributes())); + + AtomicReference> valueConstraint = new AtomicReference<>(optionalRegionAttributes + .map(RegionAttributes::getValueConstraint).orElse(null)); + + List cacheListeners = new ArrayList<>(Arrays.asList(nullSafeArray(optionalRegionAttributes + .map(RegionAttributes::getCacheListeners).orElse(null), CacheListener.class))); + + Set asyncEventQueueIds = new HashSet<>(nullSafeSet(optionalRegionAttributes + .map(RegionAttributes::getAsyncEventQueueIds).orElse(null))); + + Set gatewaySenderIds = new HashSet<>(nullSafeSet(optionalRegionAttributes + .map(RegionAttributes::getGatewaySenderIds).orElse(null))); + + when(mockRegionFactory.addAsyncEventQueueId(anyString())) + .thenAnswer(newAdder(asyncEventQueueIds, mockRegionFactory)); + + when(mockRegionFactory.addCacheListener(any(CacheListener.class))) + .thenAnswer(newAdder(cacheListeners, mockRegionFactory)); + + when(mockRegionFactory.addGatewaySenderId(anyString())) + .thenAnswer(newAdder(gatewaySenderIds, mockRegionFactory)); + + when(mockRegionFactory.initCacheListeners(any(CacheListener[].class))).thenAnswer(invocation -> { + cacheListeners.clear(); + Collections.addAll(cacheListeners, invocation.getArgument(0)); + return mockRegionFactory; + }); + + when(mockRegionFactory.setCacheLoader(any(CacheLoader.class))) + .thenAnswer(newSetter(cacheLoader, mockRegionFactory)); + + when(mockRegionFactory.setCacheWriter(any(CacheWriter.class))) + .thenAnswer(newSetter(cacheWriter, mockRegionFactory)); + + when(mockRegionFactory.setCloningEnabled(anyBoolean())) + .thenAnswer(newSetter(cloningEnabled, mockRegionFactory)); + + when(mockRegionFactory.setCompressor(any(Compressor.class))) + .thenAnswer(newSetter(compressor, mockRegionFactory)); + + when(mockRegionFactory.setConcurrencyChecksEnabled(anyBoolean())) + .then(newSetter(concurrencyChecksEnabled, mockRegionFactory)); + + when(mockRegionFactory.setConcurrencyLevel(anyInt())) + .thenAnswer(newSetter(concurrencyLevel, mockRegionFactory)); + + when(mockRegionFactory.setCustomEntryIdleTimeout(any(CustomExpiry.class))) + .thenAnswer(newSetter(customEntryIdleTimeout, mockRegionFactory)); + + when(mockRegionFactory.setCustomEntryTimeToLive(any(CustomExpiry.class))) + .thenAnswer(newSetter(customEntryTimeToLive, mockRegionFactory)); + + when(mockRegionFactory.setDataPolicy(any(DataPolicy.class))) + .thenAnswer(newSetter(dataPolicy, mockRegionFactory)); + + when(mockRegionFactory.setDiskStoreName(anyString())).thenAnswer(newSetter(diskStoreName, mockRegionFactory)); + + when(mockRegionFactory.setDiskSynchronous(anyBoolean())) + .thenAnswer(newSetter(diskSynchronous, mockRegionFactory)); + + when(mockRegionFactory.setEnableAsyncConflation(anyBoolean())) + .thenAnswer(newSetter(enableAsyncConflation, mockRegionFactory)); + + when(mockRegionFactory.setEnableSubscriptionConflation(anyBoolean())) + .thenAnswer(newSetter(enableSubscriptionConflation, mockRegionFactory)); + + when(mockRegionFactory.setEntryIdleTimeout(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(entryIdleTimeout, mockRegionFactory)); + + when(mockRegionFactory.setEntryTimeToLive(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(entryTimeToLive, mockRegionFactory)); + + when(mockRegionFactory.setEvictionAttributes(any(EvictionAttributes.class))) + .thenAnswer(newSetter(evictionAttributes, mockRegionFactory)); + + when(mockRegionFactory.setIgnoreJTA(anyBoolean())).thenAnswer(newSetter(ignoreJta, mockRegionFactory)); + + when(mockRegionFactory.setIndexMaintenanceSynchronous(anyBoolean())) + .thenAnswer(newSetter(indexMaintenanceSynchronous, mockRegionFactory)); + + when(mockRegionFactory.setInitialCapacity(anyInt())).thenAnswer(newSetter(initialCapacity, mockRegionFactory)); + + when(mockRegionFactory.setKeyConstraint(any(Class.class))) + .thenAnswer(newSetter(keyConstraint, mockRegionFactory)); + + when(mockRegionFactory.setLoadFactor(anyFloat())).thenAnswer(newSetter(loadFactor, mockRegionFactory)); + + when(mockRegionFactory.setLockGrantor(anyBoolean())).thenAnswer(newSetter(lockGrantor, mockRegionFactory)); + + when(mockRegionFactory.setMulticastEnabled(anyBoolean())) + .thenAnswer(newSetter(multicastEnabled, mockRegionFactory)); + + when(mockRegionFactory.setOffHeap(anyBoolean())).thenAnswer(newSetter(offHeap, mockRegionFactory)); + + when(mockRegionFactory.setPartitionAttributes(any(PartitionAttributes.class))) + .thenAnswer(newSetter(partitionAttributes, mockRegionFactory)); + + when(mockRegionFactory.setPoolName(anyString())).thenAnswer(newSetter(poolName, mockRegionFactory)); + + when(mockRegionFactory.setRegionIdleTimeout(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(regionIdleTimeout, mockRegionFactory)); + + when(mockRegionFactory.setRegionTimeToLive(any(ExpirationAttributes.class))) + .thenAnswer(newSetter(regionTimeToLive, mockRegionFactory)); + + when(mockRegionFactory.setScope(any(Scope.class))).thenAnswer(newSetter(scope, mockRegionFactory)); + + when(mockRegionFactory.setStatisticsEnabled(anyBoolean())) + .thenAnswer(newSetter(statisticsEnabled, mockRegionFactory)); + + when(mockRegionFactory.setSubscriptionAttributes(any(SubscriptionAttributes.class))) + .thenAnswer(newSetter(subscriptionAttributes, mockRegionFactory)); + + when(mockRegionFactory.setValueConstraint(any(Class.class))) + .thenAnswer(newSetter(valueConstraint, mockRegionFactory)); + + RegionAttributes mockRegionAttributes = + mock(RegionAttributes.class, mockObjectIdentifier("MockRegionAttributes")); + + when(mockRegionAttributes.getAsyncEventQueueIds()).thenReturn(asyncEventQueueIds); + + when(mockRegionAttributes.getCacheListeners()) + .thenAnswer(newGetter(() -> cacheListeners.toArray(new CacheListener[cacheListeners.size()]))); + + when(mockRegionAttributes.getCacheLoader()).thenAnswer(newGetter(cacheLoader)); + when(mockRegionAttributes.getCacheWriter()).thenAnswer(newGetter(cacheWriter)); + when(mockRegionAttributes.getCloningEnabled()).thenAnswer(newGetter(cloningEnabled)); + when(mockRegionAttributes.getCompressor()).thenAnswer(newGetter(compressor)); + when(mockRegionAttributes.getConcurrencyChecksEnabled()).thenAnswer(newGetter(concurrencyChecksEnabled)); + when(mockRegionAttributes.getConcurrencyLevel()).thenAnswer(newGetter(concurrencyLevel)); + when(mockRegionAttributes.getCustomEntryIdleTimeout()).thenAnswer(newGetter(customEntryIdleTimeout)); + when(mockRegionAttributes.getCustomEntryTimeToLive()).thenAnswer(newGetter(customEntryTimeToLive)); + when(mockRegionAttributes.getDataPolicy()).thenAnswer(newGetter(dataPolicy)); + when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName)); + when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous)); + when(mockRegionAttributes.getEnableAsyncConflation()).thenAnswer(newGetter(enableAsyncConflation)); + when(mockRegionAttributes.getEnableSubscriptionConflation()).thenAnswer(newGetter(enableSubscriptionConflation)); + when(mockRegionAttributes.getEntryIdleTimeout()).thenAnswer(newGetter(entryIdleTimeout)); + when(mockRegionAttributes.getEntryTimeToLive()).thenAnswer(newGetter(entryTimeToLive)); + when(mockRegionAttributes.getEvictionAttributes()).thenAnswer(newGetter(evictionAttributes)); + when(mockRegionAttributes.getGatewaySenderIds()).thenReturn(gatewaySenderIds); + when(mockRegionAttributes.getIgnoreJTA()).thenAnswer(newGetter(ignoreJta)); + when(mockRegionAttributes.getIndexMaintenanceSynchronous()).thenAnswer(newGetter(indexMaintenanceSynchronous)); + when(mockRegionAttributes.getInitialCapacity()).thenAnswer(newGetter(initialCapacity)); + when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint)); + when(mockRegionAttributes.getLoadFactor()).thenAnswer(newGetter(loadFactor)); + when(mockRegionAttributes.isLockGrantor()).thenAnswer(newGetter(lockGrantor)); + when(mockRegionAttributes.getMulticastEnabled()).thenAnswer(newGetter(multicastEnabled)); + when(mockRegionAttributes.getOffHeap()).thenAnswer(newGetter(offHeap)); + when(mockRegionAttributes.getPartitionAttributes()).thenAnswer(newGetter(partitionAttributes)); + when(mockRegionAttributes.getPoolName()).thenAnswer(newGetter(poolName)); + when(mockRegionAttributes.getRegionIdleTimeout()).thenAnswer(newGetter(regionIdleTimeout)); + when(mockRegionAttributes.getRegionTimeToLive()).thenAnswer(newGetter(regionTimeToLive)); + when(mockRegionAttributes.getScope()).thenAnswer(newGetter(scope)); + when(mockRegionAttributes.getStatisticsEnabled()).thenAnswer(newGetter(statisticsEnabled)); + when(mockRegionAttributes.getSubscriptionAttributes()).thenAnswer(newGetter(subscriptionAttributes)); + when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint)); + + when(mockRegionFactory.create(anyString())).thenAnswer(invocation -> + mockRegion(mockCache, invocation.getArgument(0), mockRegionAttributes)); + + when(mockRegionFactory.createSubregion(any(Region.class), anyString())).thenAnswer(invocation -> + mockSubRegion(invocation.getArgument(0), invocation.getArgument(1), mockRegionAttributes)); + + return mockRegionFactory; + } + + public static ResourceManager mockResourceManager() { + + ResourceManager mockResourceManager = mock(ResourceManager.class); + + AtomicReference criticalHeapPercentage = + new AtomicReference<>(ResourceManager.DEFAULT_CRITICAL_PERCENTAGE); + + AtomicReference criticalOffHeapPercentage = + new AtomicReference<>(ResourceManager.DEFAULT_CRITICAL_PERCENTAGE); + + AtomicReference evictionHeapPercentage = + new AtomicReference<>(ResourceManager.DEFAULT_EVICTION_PERCENTAGE); + + AtomicReference evictionOffHeapPercentage = + new AtomicReference<>(ResourceManager.DEFAULT_EVICTION_PERCENTAGE); + + doAnswer(newSetter(criticalHeapPercentage, null)) + .when(mockResourceManager).setCriticalHeapPercentage(anyFloat()); + + doAnswer(newSetter(criticalOffHeapPercentage, null)) + .when(mockResourceManager).setCriticalOffHeapPercentage(anyFloat()); + + doAnswer(newSetter(evictionHeapPercentage, null)) + .when(mockResourceManager).setEvictionHeapPercentage(anyFloat()); + + doAnswer(newSetter(evictionOffHeapPercentage, null)) + .when(mockResourceManager).setEvictionOffHeapPercentage(anyFloat()); + + when(mockResourceManager.getCriticalHeapPercentage()).thenAnswer(newGetter(criticalHeapPercentage)); + when(mockResourceManager.getCriticalOffHeapPercentage()).thenAnswer(newGetter(criticalOffHeapPercentage)); + when(mockResourceManager.getEvictionHeapPercentage()).thenAnswer(newGetter(evictionHeapPercentage)); + when(mockResourceManager.getEvictionOffHeapPercentage()).thenAnswer(newGetter(evictionOffHeapPercentage)); + when(mockResourceManager.getRebalanceOperations()).thenReturn(Collections.emptySet()); + + return mockResourceManager; + } + + public static CacheFactory spyOn(CacheFactory cacheFactory) { + return spyOn(cacheFactory, DEFAULT_USE_SINGLETON_CACHE); + } + + public static CacheFactory spyOn(CacheFactory cacheFactory, boolean useSingletonCache) { + + CacheFactory cacheFactorySpy = spy(cacheFactory); + + Cache resolvedMockCache = MockGemFireObjectsSupport.resolveMockedGemFireCache(useSingletonCache) + .orElseGet(() -> { + + Cache mockCache = mockPeerCache(); + + AtomicBoolean pdxIgnoreUnreadFields = new AtomicBoolean(false); + AtomicBoolean pdxPersistent = new AtomicBoolean(false); + AtomicBoolean pdxReadSerialized = new AtomicBoolean(false); + + AtomicReference pdxDiskStoreName = new AtomicReference<>(null); + AtomicReference pdxSerializer = new AtomicReference<>(null); + + doAnswer(newSetter(pdxDiskStoreName, cacheFactorySpy)) + .when(cacheFactorySpy).setPdxDiskStore(anyString()); + + doAnswer(newSetter(pdxIgnoreUnreadFields, cacheFactorySpy)) + .when(cacheFactorySpy).setPdxIgnoreUnreadFields(anyBoolean()); + + doAnswer(newSetter(pdxPersistent, cacheFactorySpy)) + .when(cacheFactorySpy).setPdxPersistent(anyBoolean()); + + doAnswer(newSetter(pdxReadSerialized, cacheFactorySpy)) + .when(cacheFactorySpy).setPdxReadSerialized(anyBoolean()); + + doAnswer(newSetter(pdxSerializer, cacheFactorySpy)) + .when(cacheFactorySpy).setPdxSerializer(any(PdxSerializer.class)); + + when(mockCache.getPdxDiskStore()).thenAnswer(newGetter(pdxDiskStoreName)); + when(mockCache.getPdxIgnoreUnreadFields()).thenAnswer(newGetter(pdxIgnoreUnreadFields)); + when(mockCache.getPdxPersistent()).thenAnswer(newGetter(pdxPersistent)); + when(mockCache.getPdxReadSerialized()).thenAnswer(newGetter(pdxReadSerialized)); + when(mockCache.getPdxSerializer()).thenAnswer(newGetter(pdxSerializer)); + + return mockCache; + }); + + doReturn(rememberMockedGemFireCache(resolvedMockCache, useSingletonCache)).when(cacheFactorySpy).create(); + + return cacheFactorySpy; + } + + public static ClientCacheFactory spyOn(ClientCacheFactory clientCacheFactory) { + return spyOn(clientCacheFactory, DEFAULT_USE_SINGLETON_CACHE); + } + + public static ClientCacheFactory spyOn(ClientCacheFactory clientCacheFactory, boolean useSingletonCache) { + + ClientCacheFactory clientCacheFactorySpy = spy(clientCacheFactory); + + ClientCache resolvedMockedClientCache = + MockGemFireObjectsSupport.resolveMockedGemFireCache(useSingletonCache).orElseGet(() -> { + + ClientCache mockClientCache = mockClientCache(); + + AtomicBoolean pdxIgnoreUnreadFields = new AtomicBoolean(false); + AtomicBoolean pdxPersistent = new AtomicBoolean(false); + AtomicBoolean pdxReadSerialized = new AtomicBoolean(false); + + AtomicReference pdxDiskStoreName = new AtomicReference<>(null); + AtomicReference pdxSerializer = new AtomicReference<>(null); + AtomicReference defaultPool = new AtomicReference<>(null); + + doAnswer(newSetter(pdxDiskStoreName, clientCacheFactorySpy)) + .when(clientCacheFactorySpy).setPdxDiskStore(anyString()); + + doAnswer(newSetter(pdxIgnoreUnreadFields, clientCacheFactorySpy)) + .when(clientCacheFactorySpy).setPdxIgnoreUnreadFields(anyBoolean()); + + doAnswer(newSetter(pdxPersistent, clientCacheFactorySpy)) + .when(clientCacheFactorySpy).setPdxPersistent(anyBoolean()); + + doAnswer(newSetter(pdxReadSerialized, clientCacheFactorySpy)) + .when(clientCacheFactorySpy).setPdxReadSerialized(anyBoolean()); + + doAnswer(newSetter(pdxSerializer, clientCacheFactorySpy)) + .when(clientCacheFactorySpy).setPdxSerializer(any(PdxSerializer.class)); + + PoolFactory mockPoolFactory = mockPoolFactory(); + + doAnswer(invocation -> { + mockPoolFactory.addLocator(invocation.getArgument(0), invocation.getArgument(1)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).addPoolLocator(anyString(), anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.addServer(invocation.getArgument(0), invocation.getArgument(1)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).addPoolServer(anyString(), anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setFreeConnectionTimeout(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolFreeConnectionTimeout(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setIdleTimeout(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolIdleTimeout(anyLong()); + + doAnswer(invocation -> { + mockPoolFactory.setLoadConditioningInterval(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolLoadConditioningInterval(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setMaxConnections(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolMaxConnections(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setMinConnections(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolMinConnections(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setMultiuserAuthentication(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolMultiuserAuthentication(anyBoolean()); + + doAnswer(invocation -> { + mockPoolFactory.setPingInterval(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolPingInterval(anyLong()); + + doAnswer(invocation -> { + mockPoolFactory.setPRSingleHopEnabled(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolPRSingleHopEnabled(anyBoolean()); + + doAnswer(invocation -> { + mockPoolFactory.setReadTimeout(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolReadTimeout(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setRetryAttempts(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolRetryAttempts(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setServerGroup(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolServerGroup(anyString()); + + doAnswer(invocation -> { + mockPoolFactory.setSocketBufferSize(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolSocketBufferSize(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setStatisticInterval(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolStatisticInterval(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setSubscriptionAckInterval(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolSubscriptionAckInterval(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setSubscriptionEnabled(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolSubscriptionEnabled(anyBoolean()); + + doAnswer(invocation -> { + mockPoolFactory.setSubscriptionMessageTrackingTimeout(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolSubscriptionMessageTrackingTimeout(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setSubscriptionRedundancy(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolSubscriptionRedundancy(anyInt()); + + doAnswer(invocation -> { + mockPoolFactory.setThreadLocalConnections(invocation.getArgument(0)); + return clientCacheFactorySpy; + }).when(clientCacheFactorySpy).setPoolThreadLocalConnections(anyBoolean()); + + when(mockClientCache.getCurrentServers()).thenAnswer(invocation -> + Collections.unmodifiableSet(new HashSet<>(defaultPool.get().getServers()))); + + when(mockClientCache.getDefaultPool()).thenAnswer(invocation -> { + + if (defaultPool.get() == null) { + defaultPool.set(mockPoolFactory.create("DEFAULT")); + } + + return defaultPool.get(); + }); + + when(mockClientCache.getPdxDiskStore()).thenAnswer(newGetter(pdxDiskStoreName)); + when(mockClientCache.getPdxIgnoreUnreadFields()).thenAnswer(newGetter(pdxIgnoreUnreadFields)); + when(mockClientCache.getPdxPersistent()).thenAnswer(newGetter(pdxPersistent)); + when(mockClientCache.getPdxReadSerialized()).thenAnswer(newGetter(pdxReadSerialized)); + when(mockClientCache.getPdxSerializer()).thenAnswer(newGetter(pdxSerializer)); + + return mockClientCache; + }); + + doReturn(rememberMockedGemFireCache(resolvedMockedClientCache, useSingletonCache)) + .when(clientCacheFactorySpy).create(); + + return clientCacheFactorySpy; + } + + protected interface IoExceptionThrowingOperation { + void doIo() throws IOException; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/MockObjectsSupport.java b/src/main/java/org/springframework/data/gemfire/tests/mock/MockObjectsSupport.java new file mode 100644 index 0000000..869cc04 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/MockObjectsSupport.java @@ -0,0 +1,189 @@ +/* + * Copyright 2017 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 2.0.0 + */ +@SuppressWarnings("unused") +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) { + return String.format("%s%d", Optional.ofNullable(mockObjectName).filter(StringUtils::hasText) + .orElse(DEFAULT_MOCK_OBJECT_NAME), mockObjectIdentifier.incrementAndGet()); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicBoolean returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicInteger returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicLong returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicReference returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(AtomicReference returnValue, Function converter) { + return invocation -> converter.apply(returnValue.get()); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(Supplier returnValue) { + return invocation -> returnValue.get(); + } + + /* (non-Javadoc) */ + protected static Answer newGetter(Supplier returnValue, Function converter) { + return invocation -> converter.apply(returnValue.get()); + } + + /* (non-Javadoc) */ + protected static , R> Answer newAdder(C collection, R returnValue) { + return invocation -> { + collection.add(invocation.getArgument(0)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicBoolean argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgument(0)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicBoolean argument, Boolean value, R returnValue) { + return invocation -> { + argument.set(value); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicInteger argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgument(0)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicInteger argument, Integer value, R returnValue) { + return invocation -> { + argument.set(value); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicLong argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgument(0)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicLong argument, Long value, R returnValue) { + return invocation -> { + argument.set(value); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicReference argument, R returnValue) { + return invocation -> { + argument.set(invocation.getArgument(0)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicReference argument, T value, R returnValue) { + return invocation -> { + argument.set(value); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(AtomicReference argument, Function converter, R returnValue) { + return invocation -> { + argument.set(converter.apply(invocation.getArgument(0))); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newSetter(Map argument, R returnValue) { + return invocation -> { + argument.put(invocation.getArgument(0), invocation.getArgument(1)); + return returnValue; + }; + } + + /* (non-Javadoc) */ + protected static Answer newVoidAnswer(Consumer methodInvocation) { + return invocation -> { + methodInvocation.accept(invocation); + return null; + }; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/EnableGemFireMockObjects.java b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/EnableGemFireMockObjects.java new file mode 100644 index 0000000..1923a9f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/EnableGemFireMockObjects.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(GemFireMockObjectsConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableGemFireMockObjects { + + /** + * Determines whether the mock {@link GemFireCache} created for Unit Tests is a Singleton. + * + * Defaults to {@literal false}. + * + * @return a boolean value indicating whether the mock {@link GemFireCache} created for Unit Tests is a Singleton. + */ + boolean useSingletonCache() default false; + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java new file mode 100644 index 0000000..d5673cd --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017 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 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.MockGemFireObjectsSupport; +import org.springframework.data.gemfire.tests.mock.config.MockGemFireObjectsBeanPostProcessor; + +/** + * The {@link GemFireMockObjectsConfiguration} class is a Spring {@link Configuration @Configuration} class + * containing bean definitions to configure GemFire Object mocking. + * + * @author John Blum + * @see 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.test.mock.config.MockGemFireObjectsBeanPostProcessor + * @since 2.0.0 + */ +@SuppressWarnings("unused") +@Configuration +public class GemFireMockObjectsConfiguration implements ImportAware { + + private boolean useSingletonCache = false; + + @Override + public void setImportMetadata(AnnotationMetadata importingClassMetadata) { + + if (isAnnotationPresent(importingClassMetadata)) { + + AnnotationAttributes enableGemFireMockingAttributes = getAnnotationAttributes(importingClassMetadata); + + this.useSingletonCache = enableGemFireMockingAttributes.getBoolean("useSingletonCache"); + } + } + + private Class getAnnotationType() { + return EnableGemFireMockObjects.class; + } + + private boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata) { + return isAnnotationPresent(importingClassMetadata, getAnnotationType()); + } + + private boolean isAnnotationPresent(AnnotationMetadata importingClassMetadata, + Class annotationType) { + + return importingClassMetadata.hasAnnotation(annotationType.getName()); + } + + private AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata) { + return getAnnotationAttributes(importingClassMetadata, getAnnotationType()); + } + + private AnnotationAttributes getAnnotationAttributes(AnnotationMetadata importingClassMetadata, + Class annotationType) { + + return AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(annotationType.getName())); + } + + @Bean + public BeanPostProcessor mockGemFireObjectsBeanPostProcessor() { + return MockGemFireObjectsBeanPostProcessor.newInstance(this.useSingletonCache); + } + + @EventListener + public void releaseMockResources(ContextClosedEvent event) { + MockGemFireObjectsSupport.destroy(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireUnitTest.java b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireUnitTest.java new file mode 100644 index 0000000..67a1d75 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireUnitTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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.MockGemFireObjectsApplicationContextInitializer; +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 Documented + * @see Inherited + * @see Retention + * @see Target + * @see org.junit.runner.RunWith + * @see org.springframework.data.gemfire.test.mock.context.MockGemFireObjectsApplicationContextInitializer + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@RunWith(SpringRunner.class) +@ContextConfiguration(initializers = MockGemFireObjectsApplicationContextInitializer.class) +@SuppressWarnings("unused") +public @interface GemFireUnitTest { + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/config/MockGemFireObjectsBeanPostProcessor.java b/src/main/java/org/springframework/data/gemfire/tests/mock/config/MockGemFireObjectsBeanPostProcessor.java new file mode 100644 index 0000000..813f1e7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/config/MockGemFireObjectsBeanPostProcessor.java @@ -0,0 +1,173 @@ +/* + * Copyright 2017 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.MockGemFireObjectsSupport; +import org.springframework.lang.Nullable; + +/** + * The {@link MockGemFireObjectsBeanPostProcessor} 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.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.test.mock.MockGemFireObjectsSupport + * @since 2.0.0 + */ +public class MockGemFireObjectsBeanPostProcessor 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 gemfireProperties = new AtomicReference<>(new Properties()); + + public static MockGemFireObjectsBeanPostProcessor newInstance() { + return newInstance(DEFAULT_USE_SINGLETON_CACHE); + } + + public static MockGemFireObjectsBeanPostProcessor newInstance(boolean useSingletonCache) { + + MockGemFireObjectsBeanPostProcessor beanPostProcessor = new MockGemFireObjectsBeanPostProcessor(); + + 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 { + + public 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; + } + + @Override + public CacheFactory initialize(CacheFactory cacheFactory) { + return MockGemFireObjectsSupport.spyOn(cacheFactory, useSingletonCache); + } + } + + protected static class SpyingClientCacheFactoryInitializer + implements CacheFactoryBean.CacheFactoryInitializer { + + public 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; + } + + @Override + public ClientCacheFactory initialize(ClientCacheFactory clientCacheFactory) { + return MockGemFireObjectsSupport.spyOn(clientCacheFactory, this.useSingletonCache); + } + } + + protected static class MockingPoolFactoryInitializer implements PoolFactoryBean.PoolFactoryInitializer { + + public static PoolFactoryBean mock(PoolFactoryBean poolFactoryBean) { + poolFactoryBean.setPoolFactoryInitializer(new MockingPoolFactoryInitializer()); + return poolFactoryBean; + } + + @Override + public PoolFactory initialize(PoolFactory poolFactory) { + return MockGemFireObjectsSupport.mockPoolFactory(); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/context/MockGemFireObjectsApplicationContextInitializer.java b/src/main/java/org/springframework/data/gemfire/tests/mock/context/MockGemFireObjectsApplicationContextInitializer.java new file mode 100644 index 0000000..92dc79d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/context/MockGemFireObjectsApplicationContextInitializer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017 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.MockGemFireObjectsBeanPostProcessor; + +/** + * The {@link MockGemFireObjectsApplicationContextInitializer} 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.test.mock.config.MockGemFireObjectsBeanPostProcessor + * @since 2.0.0 + */ +public class MockGemFireObjectsApplicationContextInitializer + implements ApplicationContextInitializer { + + @Override + @SuppressWarnings("all") + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.getBeanFactory().addBeanPostProcessor(MockGemFireObjectsBeanPostProcessor.newInstance()); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectInvocationException.java b/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectInvocationException.java new file mode 100644 index 0000000..5b727d4 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectInvocationException.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 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.test.mock.support.MockObjectsException + * @since 2.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); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectsException.java b/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectsException.java new file mode 100644 index 0000000..8f45784 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/mock/support/MockObjectsException.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 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 2.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); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java b/src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java new file mode 100644 index 0000000..aa30019 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/PidUnavailableException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2013 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 PidUnavailableException class is a RuntimeException indicating that the process ID (PID) is unobtainable for + * the current process. + * + * @author John Blum + * @see RuntimeException + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public class PidUnavailableException extends RuntimeException { + + public PidUnavailableException() { + } + + public PidUnavailableException(final String message) { + super(message); + } + + public PidUnavailableException(final Throwable cause) { + super(cause); + } + + public PidUnavailableException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java new file mode 100644 index 0000000..4da7b94 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010-2013 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 ProcessBuilder + * @see org.springframework.data.gemfire.process.ProcessExecutor + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public class ProcessConfiguration { + + private final boolean redirectingErrorStream; + + private final File workingDirectory; + + private final List command; + + private final Map 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 command, File workingDirectory, Map environment, + boolean redirectErrorStream) { + + Assert.notEmpty(command, "Process command must be specified"); + + Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), String.format( + "Process working directory [%s] is not valid", workingDirectory)); + + this.command = new ArrayList(command); + this.workingDirectory = workingDirectory; + this.redirectingErrorStream = redirectErrorStream; + + this.environment = (environment != null + ? Collections.unmodifiableMap(new HashMap(environment)) + : Collections.emptyMap()); + } + + public List getCommand() { + return Collections.unmodifiableList(command); + } + + public String getCommandString() { + return StringUtils.arrayToDelimitedString(getCommand().toArray(), " "); + } + + public Map getEnvironment() { + return environment; + } + + public boolean isRedirectingErrorStream() { + return redirectingErrorStream; + } + + public File getWorkingDirectory() { + return 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(" }"); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java new file mode 100644 index 0000000..2c980b2 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessExecutor.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010-2013 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 1.5.0 + */ +@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_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.gemfire."; + protected static final String SPRING_DATA_GEMFIRE_SYSTEM_PROPERTY_PREFIX = "spring.data.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 command = new ArrayList<>(); + List 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 (!StringUtils.isEmpty(arg)) { + programArguments.add(arg); + } + } + + command.add(type.getName()); + command.addAll(programArguments); + + return command.toArray(new String[command.size()]); + } + + protected static Collection 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 File validateDirectory(File workingDirectory) { + Assert.isTrue(workingDirectory != null && (workingDirectory.isDirectory() || workingDirectory.mkdirs()), + String.format("Failed to create working directory [%s]", workingDirectory)); + + return workingDirectory; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java new file mode 100644 index 0000000..7340524 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessInputStreamListener.java @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2013 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 EventListener + * @since 1.5.0 + */ +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 Process#getErrorStream() + * @see Process#getInputStream() + */ + void onInput(String input); + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java new file mode 100644 index 0000000..5a0af73 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessUtils.java @@ -0,0 +1,233 @@ +/* + * Copyright 2010-2013 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 1.5.0 + */ +@SuppressWarnings("unused") +public abstract class ProcessUtils { + + protected static final Logger log = Logger.getLogger(ProcessUtils.class.getName()); + + protected static final String TERM_TOKEN = ""; + + /* (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 PidUnavailableException(String.format("Process ID (PID) not available [%s]", runtimeMXBeanName), + cause); + } + + /* (non-Javadoc) */ + public static boolean isAlive(Process process) { + return (process != null && process.isAlive()); + } + + /* (non-Javadoc) */ + public static boolean isRunning(int processId) { + /* + for (VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()) { + if (String.valueOf(processId).equals(vmDescriptor.id())) { + return true; + } + } + + return false; + */ + + throw new UnsupportedOperationException("operation not supported"); + } + + /* (non-Javadoc) */ + public static boolean isRunning(Process process) { + try { + process.exitValue(); + return false; + } + catch (IllegalThreadStateException ignore) { + return true; + } + } + + /* (non-Javadoc) */ + public static void signalStop(Process process) throws IOException { + if (isRunning(process)) { + OutputStream processOutputStream = process.getOutputStream(); + processOutputStream.write(TERM_TOKEN.concat("\n").getBytes()); + processOutputStream.flush(); + } + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + public static void waitForStopSignal() { + Scanner in = new Scanner(System.in); + while (!TERM_TOKEN.equals(in.next())); + } + + /* (non-Javadoc) */ + public static int findAndReadPid(File workingDirectory) { + File pidFile = findPidFile(workingDirectory); + + if (pidFile == null) { + throw new PidUnavailableException(String.format( + "No PID file was found in working directory [%s] or any of it's sub-directories", + workingDirectory)); + } + + return readPid(pidFile); + } + + /* (non-Javadoc) */ + @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; + } + + /* (non-Javadoc) */ + @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 e) { + throw new PidUnavailableException(String.format("PID file [%s] not found", pidFile), e); + } + catch (IOException e) { + throw new PidUnavailableException(String.format("failed to read PID from file [%s]", pidFile), e); + } + catch (NumberFormatException e) { + throw new PidUnavailableException(String.format( + "value [%1$s] from PID file [%2$s] was not a valid numerical PID", pidValue, pidFile), e); + } + finally { + IOUtils.close(fileReader); + } + } + + /* (non-Javadoc) */ + @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); + } + } + + /* (non-Javadoc) */ + protected static class DirectoryPidFileFilter extends PidFileFilter { + + protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter(); + + /** + * @inheritDoc + */ + @Override + public boolean accept(File path) { + return (path != null && (path.isDirectory() || super.accept(path))); + } + } + + /* (non-Javadoc) */ + protected static class PidFileFilter implements FileFilter { + + protected static final PidFileFilter INSTANCE = new PidFileFilter(); + + /** + * @inheritDoc + */ + @Override + public boolean accept(File path) { + return (path != null && path.isFile() && path.getName().toLowerCase().endsWith(".pid")); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java new file mode 100644 index 0000000..402406d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/process/ProcessWrapper.java @@ -0,0 +1,347 @@ +/* + * Copyright 2010-2013 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 1.5.0 + */ +@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 listeners = new CopyOnWriteArrayList<>(); + + protected final Logger log = Logger.getLogger(getClass().getName()); + + private final Process process; + private final ProcessConfiguration processConfiguration; + + /* (non-Javadoc) */ + public ProcessWrapper(Process process, ProcessConfiguration processConfiguration) { + Assert.notNull(process, "Process must not be null"); + + 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" + + " must not be null"); + + this.process = process; + this.processConfiguration = processConfiguration; + + init(); + } + + /* (non-Javadoc) */ + 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(); + } + } + + /* (non-Javadoc) */ + protected 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 : 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); + } + } + }; + } + + /* (non-Javadoc) */ + protected Thread newThread(String name, Runnable task) { + Assert.hasText(name, "Thread name must be specified"); + Assert.notNull(task, "Thread task must not be null"); + + Thread thread = new Thread(task, name); + + thread.setDaemon(DEFAULT_DAEMON_THREAD); + thread.setPriority(Thread.NORM_PRIORITY); + + return thread; + } + + /* (non-Javadoc) */ + public boolean isAlive() { + return ProcessUtils.isAlive(process); + } + + /* (non-Javadoc) */ + public boolean isNotAlive() { + return !isAlive(); + } + + /* (non-Javadoc) */ + public List getCommand() { + return processConfiguration.getCommand(); + } + + /* (non-Javadoc) */ + public String getCommandString() { + return processConfiguration.getCommandString(); + } + + /* (non-Javadoc) */ + public Map getEnvironment() { + return processConfiguration.getEnvironment(); + } + + /* (non-Javadoc) */ + public int getPid() { + return ProcessUtils.findAndReadPid(getWorkingDirectory()); + } + + /* (non-Javadoc) */ + public int safeGetPid() { + try { + return getPid(); + } + catch (PidUnavailableException ignore) { + return -1; + } + } + + /* (non-Javadoc) */ + public boolean isRedirectingErrorStream() { + return processConfiguration.isRedirectingErrorStream(); + } + + /* (non-Javadoc) */ + public boolean isNotRunning() { + return !isRunning(); + } + + /* (non-Javadoc) */ + public boolean isRunning() { + return ProcessUtils.isRunning(process); + } + + /* (non-Javadoc) */ + public File getWorkingDirectory() { + return processConfiguration.getWorkingDirectory(); + } + + /* (non-Javadoc) */ + public int exitValue() { + return process.exitValue(); + } + + /* (non-Javadoc) */ + public int safeExitValue() { + try { + return exitValue(); + } + catch (IllegalThreadStateException ignore) { + return -1; + } + } + + /* (non-Javadoc) */ + 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())); + } + } + + /* (non-Javadoc) */ + public String readLogFile(File log) throws IOException { + return FileUtils.read(log); + } + + /* (non-Javadoc) */ + public boolean register(ProcessInputStreamListener listener) { + return (listener != null && listeners.add(listener)); + } + + /* (non-Javadoc) */ + public void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); + } + + /* (non-Javadoc) */ + public void signal() { + try { + OutputStream outputStream = process.getOutputStream(); + outputStream.write("\n".getBytes()); + outputStream.flush(); + } + catch (IOException e) { + log.warning("Failed to signal process"); + + if (log.isLoggable(Level.FINE)) { + log.fine(ThrowableUtils.toString(e)); + } + } + } + + /* (non-Javadoc) */ + public void signalStop() { + try { + ProcessUtils.signalStop(process); + } + catch (IOException e) { + log.warning("Failed to signal the process to stop"); + + if (log.isLoggable(Level.FINE)) { + log.fine(ThrowableUtils.toString(e)); + } + } + } + + /* (non-Javadoc) */ + public int stop() { + return stop(DEFAULT_WAIT_TIME_MILLISECONDS); + } + + /* (non-Javadoc) */ + 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 futureExitValue = executorService.submit(() -> { + process.destroy(); + int localExitValue = process.waitFor(); + exited.set(true); + return localExitValue; + }); + + while (!exited.get() && System.currentTimeMillis() < timeout) { + try { + exitValue = futureExitValue.get(milliseconds, TimeUnit.MILLISECONDS); + log.info(String.format("Process [%s] has stopped%n", pid)); + } + catch (InterruptedException ignore) { + interrupted = true; + } + } + } + catch (TimeoutException e) { + exitValue = -1; + 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(); + } + } + + /* (non-Javadoc) */ + public int shutdown() { + if (isRunning()) { + log.info(String.format("Stopping process [%d]...%n", safeGetPid())); + signalStop(); + waitFor(); + } + + return stop(); + } + + /* (non-Javadoc) */ + public boolean unregister(ProcessInputStreamListener listener) { + return listeners.remove(listener); + } + + /* (non-Javadoc) */ + public void waitFor() { + waitFor(DEFAULT_WAIT_TIME_MILLISECONDS); + } + + /* (non-Javadoc) */ + public void waitFor(long milliseconds) { + ThreadUtils.timedWait(milliseconds, 500, this::isRunning); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/support/DataSourceAdapter.java b/src/main/java/org/springframework/data/gemfire/tests/support/DataSourceAdapter.java new file mode 100644 index 0000000..4eda3e0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/support/DataSourceAdapter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2013 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 DataSourceAdapter class is an implementation of the DataSource interface with unsupported operations by default. + * + * @author John Blum + * @see Connection + * @see DataSource + * @since 1.3.4 + */ +@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 unwrap(final Class iface) throws SQLException { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/support/IdentifierSequence.java b/src/main/java/org/springframework/data/gemfire/tests/support/IdentifierSequence.java new file mode 100644 index 0000000..9a7510f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/support/IdentifierSequence.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2013 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 IdentifierSequence class is an Identifier (ID) generator generating unique IDs in sequence. + * + * @author John Blum + * @see System#currentTimeMillis() + * @see AtomicLong + * @since 1.5.3 + */ +@SuppressWarnings("unused") +public abstract class IdentifierSequence { + + private static final AtomicLong ID_SEQUENCE = new AtomicLong(System.currentTimeMillis()); + + public static long nextId() { + return ID_SEQUENCE.incrementAndGet(); + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/support/MapBuilder.java b/src/main/java/org/springframework/data/gemfire/tests/support/MapBuilder.java new file mode 100644 index 0000000..5b4e73c --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/support/MapBuilder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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 2.0.0 + */ +public class MapBuilder { + + public static MapBuilder newMapBuilder() { + return new MapBuilder<>(); + } + + private final Map map = new HashMap<>(); + + public MapBuilder put(KEY key, VALUE value) { + this.map.put(key, value); + return this; + } + + public MapBuilder remove(KEY key) { + this.map.remove(key); + return this; + } + + public Map build() { + return this.map; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/FileSystemUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/FileSystemUtils.java new file mode 100644 index 0000000..33d32e0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/FileSystemUtils.java @@ -0,0 +1,249 @@ +/* + * Copyright 2010-2013 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 File + * @see FileFilter + * @see org.springframework.data.gemfire.test.support.FileUtils + * @see org.springframework.data.gemfire.test.support.IOUtils + * @since 1.5.0 + */ +@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]; + + /* (non-Javadoc) */ + public static boolean deleteRecursive(File path) { + return deleteRecursive(path, AllFilesFilter.INSTANCE); + } + + /* (non-Javadoc) */ + 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); + } + + /* (non-Javadoc) */ + 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 results = new ArrayList(); + + 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()]); + } + + /* (non-Javadoc) */ + public static File[] safeListFiles(File directory) { + return safeListFiles(directory, AllFilesFilter.INSTANCE); + } + + /* (non-Javadoc) */ + 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 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 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); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/FileUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/FileUtils.java new file mode 100644 index 0000000..4773b2f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/FileUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright 2010-2017 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 File + * @see FileReader + * @see FileWriter + * @see org.springframework.data.gemfire.test.support.IOUtils + * @since 1.5.0 + */ +@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"); + + /* (non-Javadoc) */ + public static boolean isDirectory(File path) { + return (path != null && path.isDirectory()); + } + + /* (non-Javadoc) */ + public static boolean isFile(File path) { + return (path != null && path.isFile()); + } + + /* (non-Javadoc) */ + public static File newFile(String pathname) { + return new File(pathname); + } + + /* (non-Javadoc) */ + public static File newFile(File parent, String pathname) { + return new File(parent, pathname); + } + + /* (non-Javadoc) */ + @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 must not be null"); + + 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); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/IOUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/IOUtils.java new file mode 100644 index 0000000..35dd1f0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/IOUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010-2017 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 Closeable + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public abstract class IOUtils { + + protected static final Logger log = Logger.getLogger(IOUtils.class.getName()); + + /* (non-Javadoc) */ + 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; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + public static 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); + } + } + + /* (non-Javadoc) */ + 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); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/SocketUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/SocketUtils.java new file mode 100644 index 0000000..0098253 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/SocketUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 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 ServerSocket + * @see Socket + * @since 1.9.0 + */ +@SuppressWarnings("unused") +public abstract class SocketUtils { + + private static final Logger log = Logger.getLogger(SocketUtils.class.getName()); + + /* (non-Javadoc) */ + 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; + } + + /* (non-Javadoc) */ + 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; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java new file mode 100644 index 0000000..e418300 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2013 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 StackTraceUtils class is a utility class for working with stack trace frames (elements) of the current Thread. + * + * @author John Blum + * @see StackTraceElement + * @see Thread + * @see org.springframework.data.gemfire.test.support.ThreadUtils + * @since 1.5.3 + */ +@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; + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/ThreadUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/ThreadUtils.java new file mode 100644 index 0000000..2327c8f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/ThreadUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2013 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 Thread + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public abstract class ThreadUtils { + + /* (non-Javadoc) */ + 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, new WaitCondition() { + @Override public boolean waiting() { + return 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(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/ThrowableUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/ThrowableUtils.java new file mode 100644 index 0000000..a1b081b --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/ThrowableUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2013 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 ThrowableUtils class is a utility class for working with Throwable, Exception and Error objects. + * + * @author John Blum + * @see Error + * @see Exception + * @see Throwable + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public abstract class ThrowableUtils { + + public static String toString(final Throwable t) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + +} diff --git a/src/main/java/org/springframework/data/gemfire/tests/util/ZipUtils.java b/src/main/java/org/springframework/data/gemfire/tests/util/ZipUtils.java new file mode 100644 index 0000000..a904d74 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/tests/util/ZipUtils.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2013 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 ZipUtils class is an abstract utility class for working with JAR and ZIP archives. + * + * @author John Blum + * @see File + * @see ZipFile + * @since 1.5.0 + */ +public abstract class ZipUtils { + + public static void unzip(final Resource zipResource, final File directory) throws IOException { + Assert.notNull(zipResource, "The ZIP Resource must not be null!"); + + 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); + } + } + } + } +}