Add integration test to assert that no Spring (Session Data GemFire/Geode) artifacts of any kind are need on the classpath of a GemFire/Geode Server when PDX Serialization is used.

Resolves Issue #2.
This commit is contained in:
John Blum
2017-09-30 14:05:24 -07:00
parent 6581c790d0
commit 5945845f87
3 changed files with 424 additions and 11 deletions

View File

@@ -23,9 +23,12 @@ import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@@ -50,6 +53,7 @@ import org.springframework.context.ApplicationListener;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.support.GemFireUtils;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.util.StringUtils;
/**
* {@link AbstractGemFireIntegrationTests} is an abstract base class encapsulating common functionality
@@ -85,7 +89,7 @@ public abstract class AbstractGemFireIntegrationTests {
protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl";
protected static final String GEMFIRE_LOG_FILE_NAME =
System.getProperty("spring.session.data.gemfire.log-file", "server.log");
System.getProperty("spring.session.data.gemfire.log-file", "gemfire-server.log");
protected static final String GEMFIRE_LOG_LEVEL =
System.getProperty("spring.session.data.gemfire.log-level", "error");
@@ -101,13 +105,50 @@ public abstract class AbstractGemFireIntegrationTests {
System.setProperty("gemfire.Query.VERBOSE", String.valueOf(isQueryDebuggingEnabled()));
}
/* (non-Javadoc) */
protected static String buildClassPathContainingJarFiles(String... jarFilenames) {
StringBuilder classpath = new StringBuilder();
stream(nullSafeArray(jarFilenames, String.class)).map(AbstractGemFireIntegrationTests::findJarInClasspath)
.forEach(classpathEntry -> classpathEntry.filter(StringUtils::hasText).ifPresent(it -> {
if (classpath.length() > 0) {
classpath.append(File.pathSeparator);
}
classpath.append(it);
}));
return classpath.toString();
}
/* (non-Javadoc) */
private static Optional<URL> findClassInFileSystem(Class<?> type) {
return Optional.ofNullable(type)
.map(AbstractGemFireIntegrationTests::getResourceName)
.map(resourceName -> type.getClassLoader().getResource(resourceName));
}
/* (non-Javadoc) */
private static Optional<String> findJarInClasspath(String jarFilename) {
return stream(nullSafeArray(System.getProperty("java.class.path").split(File.pathSeparator), String.class))
.filter(element -> element.contains(jarFilename)).findFirst();
}
/* (non-Javadoc) */
private static String getResourceName(Class<?> type) {
return type.getName().replaceAll("\\.", "/").concat(".class");
}
/* (non-Javadoc) */
protected static File createDirectory(String pathname) {
File directory = new File(WORKING_DIRECTORY, pathname);
assertThat(directory.isDirectory() || directory.mkdirs())
.as(String.format("Failed to create directory (%1$s)", directory)).isTrue();
.as(String.format("Failed to create directory [%s]", directory)).isTrue();
directory.deleteOnExit();
@@ -116,11 +157,15 @@ public abstract class AbstractGemFireIntegrationTests {
/* (non-Javadoc) */
protected static List<String> createJavaProcessCommandLine(Class<?> type, String... args) {
return createJavaProcessCommandLine(System.getProperty("java.class.path"), type, args);
}
/* (non-Javadoc) */
protected static List<String> createJavaProcessCommandLine(String classpath, Class<?> type, String... args) {
List<String> commandLine = new ArrayList<>();
String javaHome = System.getProperty("java.home");
String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath();
commandLine.add(javaExe);
@@ -131,28 +176,67 @@ public abstract class AbstractGemFireIntegrationTests {
commandLine.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG));
commandLine.addAll(extractJvmArguments(args));
commandLine.add("-classpath");
commandLine.add(System.getProperty("java.class.path"));
commandLine.add(classpath);
commandLine.add(type.getName());
commandLine.addAll(extractProgramArguments(args));
// System.err.printf("Java process command-line is (%1$s)%n", commandLine);
//System.err.printf("Java process command-line is [%s]%n", commandLine);
return commandLine;
}
/* (non-Javadoc) */
protected static List<String> extractJvmArguments(String... args) {
private static List<String> extractJvmArguments(String... args) {
return stream(args).filter(arg -> arg.startsWith("-")).collect(Collectors.toList());
}
/* (non-Javadoc) */
protected static List<String> extractProgramArguments(String... args) {
private static List<String> extractProgramArguments(String... args) {
return stream(args).filter(arg -> !arg.startsWith("-")).collect(Collectors.toList());
}
/* (non-Javadoc) */
// Run Java Class in Directory with Arguments
protected static String resolveClasspath(String classpath, Class<?> type) {
return Optional.ofNullable(classpath)
.filter(StringUtils::hasText)
.filter(it -> type != null)
.flatMap(it -> findClassInFileSystem(type))
.map(url -> {
try {
return new File(url.toURI());
}
catch (URISyntaxException ignore) {
return null;
}
})
.map(File::getAbsolutePath)
.map(pathname -> {
int indexOfTypeName = pathname.indexOf(getResourceName(type));
pathname = (indexOfTypeName > -1 ? pathname.substring(0, indexOfTypeName) : pathname);
pathname = (pathname.endsWith(File.separator) ? pathname.substring(0, pathname.length() - 1) : pathname);
return pathname;
})
.map(location -> classpath.concat(File.pathSeparator).concat(location))
.orElse(classpath);
}
// Run Java Class in Directory with Arguments
protected static Process run(Class<?> type, File directory, String... args) throws IOException {
return new ProcessBuilder().command(createJavaProcessCommandLine(type, args)).directory(directory).start();
return run(createJavaProcessCommandLine(type, args), directory);
}
// Run Java Class using Classpath in Directory with Arguments
protected static Process run(String classpath, Class<?> type, File directory, String... args) throws IOException {
return run(createJavaProcessCommandLine(resolveClasspath(classpath, type), type, args), directory);
}
/* (non-Javadoc) */
private static Process run(List<String> command, File directory) throws IOException {
return new ProcessBuilder().command(command).directory(directory).inheritIO().redirectErrorStream(true).start();
}
/* (non-Javadoc) */
@@ -188,9 +272,9 @@ public abstract class AbstractGemFireIntegrationTests {
Socket socket = null;
try {
if (!connected.get()) {
if (!this.connected.get()) {
socket = new Socket(host, port);
connected.set(true);
this.connected.set(true);
}
}
catch (IOException ignore) {
@@ -214,7 +298,9 @@ public abstract class AbstractGemFireIntegrationTests {
/* (non-Javadoc) */
protected static boolean waitForClientCacheToClose(long duration) {
try {
ClientCache clientCache = ClientCacheFactory.getAnyInstance();
clientCache.close();

View File

@@ -0,0 +1,202 @@
/*
* 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.session.data.gemfire.serialization.pdx;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.apache.geode.cache.server.CacheServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.session.Session;
import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests;
import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession;
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.session.data.gemfire.server.GemFireServer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.SocketUtils;
/**
* Integration tests asserting that a GemFire/Geode Server does not require any Spring Session Data GemFire/Geode
* dependencies or any transitive dependencies when PDX serialization is in effect.
*
* /Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/bin/java -server -ea
* -Dgemfire.log-level=FINEST -Dgemfire.Query.VERBOSE=false -Dspring.session.data.gemfire.cache.server.port=34095
* -classpath /Users/jblum/.gradle/caches/modules-2/files-2.1/antlr/antlr/2.7.7/83cd2cd674a217ade95a4bb83a8a14f351f48bd0/antlr-2.7.7.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/commons-collections/commons-collections/3.2.2/8ad72fe39fa8c91eaaf12aadb21e0c3661fe26d5/commons-collections-3.2.2.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/2.5/2852e6e05fbb95076fc091f6d1780f1f8fe35e0f/commons-io-2.5.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/commons-lang/commons-lang/2.6/ce1edb914c94ebc388f086c6827e8bdeec71ac2/commons-lang-2.6.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/io.github.lukehutch/fast-classpath-scanner/2.0.11/ae34a7a5e6de8ad1f86e12f6f7ae1869fcfe9987/fast-classpath-scanner-2.0.11.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/it.unimi.dsi/fastutil/7.1.0/9835253257524c1be7ab50c057aa2d418fb72082/fastutil-7.1.0.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/javax.resource/javax.resource-api/1.7/ae40e0864eb1e92c48bf82a2a3399cbbf523fb79/javax.resource-api-1.7.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/javax.transaction/javax.transaction-api/1.2/d81aff979d603edd90dcd8db2abc1f4ce6479e3e/javax.transaction-api-1.2.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/4.5.0/55b548d3195efc5280bf1c3f17b49659c54dee40/jna-4.5.0.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.9.1/7a2999229464e7a324aa503c0a52ec0f05efe7bd/log4j-api-2.9.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.9.1/c041978c686866ee8534f538c6220238db3bb6be/log4j-core-2.9.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.geode/geode-common/1.2.1/9db253081d33f424f6e3ce0cde4b306e23e3420b/geode-common-1.2.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.geode/geode-core/1.2.1/fe853317e33dd2a1c291f29cee3c4be549f75a69/geode-core-1.2.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.geode/geode-json/1.2.1/bdb4c262e4ce6bb3b22e0f511cfb133a65fa0c04/geode-json-1.2.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.9.1/60077fe98b11e4e7cf8af9b20609326a166d6ac4/jackson-core-2.9.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.jgroups/jgroups/3.6.10.Final/fc0ff5a8a9de27ab62939956f705c2909bf86bc2/jgroups-3.6.10.Final.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.lucene/lucene-core/6.4.1/2a18924b9e0ed86b318902cb475a0b9ca4d7be5b/lucene-core-6.4.1.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.apache.shiro/shiro-core/1.4.0/6d05bd17e057fc12d278bb367c27f9cb0f3dc197/shiro-core-1.4.0.jar
* :/Users/jblum/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar
* :spring-session-data-geode/build/classes/integrationTest
* org.springframework.session.data.gemfire.server.GemFireServer
*
* @author John Blum
* @see org.junit.Test
* @see org.apache.geode.cache.server.CacheServer
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
* @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests
* @since 2.0.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration
@SuppressWarnings("unused")
public class SessionSerializationWithPdxRequiresNoServerConfigurationIntegrationTests
extends AbstractGemFireIntegrationTests {
private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
private static File processWorkingDirectory;
private static Process gemfireServer;
private static final String GEMFIRE_LOG_LEVEL = "error";
@Autowired
private GemFireOperationsSessionRepository sessionRepository;
@BeforeClass
public static void startGemFireServer() throws IOException {
long t0 = System.currentTimeMillis();
int port = SocketUtils.findAvailableTcpPort();
System.err.printf("Starting a GemFire Server running on host [localhost] listening on port [%d]%n", port);
System.setProperty("spring.session.data.gemfire.cache.server.port", String.valueOf(port));
String classpath = buildClassPathContainingJarFiles("javax.transaction-api", "antlr",
"commons-lang", "fastutil", "log4j-api", "log4j-core", "geode-common", "geode-core", "jgroups",
"shiro-core", "slf4j-api");
String processWorkingDirectoryPathname =
String.format("gemfire-server-pdx-serialization-tests-%1$s", TIMESTAMP.format(new Date()));
processWorkingDirectory = createDirectory(processWorkingDirectoryPathname);
gemfireServer = run(classpath, GemFireServer.class,
processWorkingDirectory, String.format("-Dspring.session.data.gemfire.cache.server.port=%d", port),
String.format("-Dgemfire.log-level=%s", GEMFIRE_LOG_LEVEL));
assertThat(waitForCacheServerToStart("localhost", port)).isTrue();
System.err.printf("GemFire Server [startup time = %d ms]%n", System.currentTimeMillis() - t0);
}
@AfterClass
public static void stopGemFireServer() {
Optional.ofNullable(gemfireServer).ifPresent(server -> {
server.destroy();
System.err.printf("GemFire Server [exit code = %d]%n",
waitForProcessToStop(server, processWorkingDirectory));
});
if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) {
FileSystemUtils.deleteRecursively(processWorkingDirectory);
}
assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue();
}
@Test
public void sessionOperationsIsSuccessful() {
Session session = save(createSession("jonDoe"));
assertThat(session).isInstanceOf(GemFireSession.class);
assertThat(session.getId()).isNotNull();
assertThat(session.getCreationTime()).isBeforeOrEqualTo(Instant.now());
assertThat(session.isExpired()).isFalse();
assertThat(((GemFireSession) session).getPrincipalName()).isEqualTo("jonDoe");
Session sessionById = get(session.getId());
assertThat(sessionById).isEqualTo(session);
assertThat(sessionById.isExpired()).isFalse();
Map<String, Session> sessionsByPrincipalName = this.sessionRepository.findByIndexNameAndIndexValue(
GemFireOperationsSessionRepository.PRINCIPAL_NAME_INDEX_NAME, "jonDoe");
assertThat(sessionsByPrincipalName).hasSize(1);
Session sessionByPrincipalName = sessionsByPrincipalName.values().iterator().next();
assertThat(sessionByPrincipalName).isInstanceOf(GemFireSession.class);
assertThat(sessionByPrincipalName).isEqualTo(session);
assertThat(sessionByPrincipalName.isExpired()).isFalse();
assertThat(((GemFireSession) sessionByPrincipalName).getPrincipalName()).isEqualTo("jonDoe");
}
@ClientCacheApplication(logLevel = GEMFIRE_LOG_LEVEL, subscriptionEnabled = true)
@EnableGemFireHttpSession(poolName = "DEFAULT")
static class GemFireCacheClientConfiguration {
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
ClientCacheConfigurer clientCachePoolPortConfigurer(
@Value("${spring.session.data.gemfire.cache.server.port:" + CacheServer.DEFAULT_PORT + "}") int cacheServerPort) {
return (beanName, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers(Collections.singletonList(
new ConnectionEndpoint("localhost", cacheServerPort)));
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.session.data.gemfire.server;
import java.io.IOException;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.ExpirationAction;
import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.server.CacheServer;
/**
* The {@link GemFireServer} class is a Java application class used to launch a GemFire Server
* with a peer {@link Cache}, a {@link CacheServer} and the {@literal ClusteredSpringSessions}
* {@link RegionShortcut#PARTITION} {@link Region}.
*
* @author John Blum
* @see java.util.Properties
* @see org.apache.geode.cache.Cache
* @see org.apache.geode.cache.GemFireCache
* @see org.apache.geode.cache.Region
* @see org.apache.geode.cache.server.CacheServer
* @since 2.0.0
*/
public class GemFireServer implements Runnable {
protected static final Integer GEMFIRE_CACHE_SERVER_PORT =
Integer.getInteger("spring.session.data.gemfire.cache.server.port", CacheServer.DEFAULT_PORT);
public static void main(String[] args) {
newGemFireServer(args).run();
}
private final String[] args;
public static GemFireServer newGemFireServer(String[] args) {
return new GemFireServer(args);
}
protected GemFireServer(String[] args) {
this.args = Optional.ofNullable(args)
.orElseThrow(() -> new IllegalArgumentException("GemFireServer process arguments are required"));
}
protected String[] getArguments() {
return this.args;
}
@Override
public void run() {
run(getArguments());
}
@SuppressWarnings("unused")
protected void run(String[] args) {
createClusteredSpringSessionsRegion(addCacheServer(gemfireCache(gemfireProperties())));
}
protected Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "o.s.s.d.g.server.GemFireServer");
gemfireProperties.setProperty("jmx-manager", "true");
//gemfireProperties.setProperty("log-file", "gemfire-server.log");
gemfireProperties.setProperty("log-level", "error");
return gemfireProperties;
}
protected Cache gemfireCache(Properties gemfireProperties) {
return new CacheFactory(gemfireProperties).create();
}
protected Cache addCacheServer(Cache gemfireCache) {
try {
CacheServer cacheServer = gemfireCache.addCacheServer();
cacheServer.setHostnameForClients("localhost");
cacheServer.setPort(GEMFIRE_CACHE_SERVER_PORT);
cacheServer.start();
return gemfireCache;
}
catch (IOException cause) {
throw new RuntimeException("GemFire CacheServer failed to start", cause);
}
}
protected Cache createClusteredSpringSessionsRegion(Cache gemfireCache) {
RegionFactory<Object, Object> clusteredSpringSessionsRegion =
gemfireCache.createRegionFactory(RegionShortcut.PARTITION);
clusteredSpringSessionsRegion.setEntryIdleTimeout(
new ExpirationAttributes(Long.valueOf(TimeUnit.MINUTES.toSeconds(30)).intValue(),
ExpirationAction.INVALIDATE));
clusteredSpringSessionsRegion.create("ClusteredSpringSessions");
return gemfireCache;
}
}