From 49b86a0ed020bf56e3f7604307d91bf0eefb722e Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 18 Nov 2016 15:58:56 -0800 Subject: [PATCH] SGF-569 - Move Off-Heap, Redis Adapter and Security Annotation config from apache-geode to SDG 2.0.x. --- build.gradle | 7 +- gradle.properties | 1 + pom.xml | 8 + .../asciidoc/appendix/appendix-schema.adoc | 4 +- .../ApacheShiroSecurityConfiguration.java | 253 +++++++++++ .../config/annotation/AuthConfiguration.java | 42 +- .../gemfire/config/annotation/EnableAuth.java | 3 +- .../config/annotation/EnableOffHeap.java | 67 +++ .../config/annotation/EnableRedisServer.java | 62 +++ .../config/annotation/EnableSecurity.java | 115 +++++ .../GeodeIntegratedSecurityConfiguration.java | 98 +++++ .../annotation/OffHeapConfiguration.java | 156 +++++++ .../annotation/RedisServerConfiguration.java | 52 +++ .../data/gemfire/util/CollectionUtils.java | 68 ++- .../gemfire/util/DistributedSystemUtils.java | 3 +- ...ledClientCacheRegionsIntegrationTests.java | 4 +- ...AbstractGeodeSecurityIntegrationTests.java | 404 ++++++++++++++++++ ...yManagerGeodeSecurityIntegrationTests.java | 144 +++++++ ...ShiroIniGeodeSecurityIntegrationTests.java | 54 +++ ...iroRealmGeodeSecurityIntegrationTests.java | 67 +++ ...erverCacheApplicationIntegrationTests.java | 93 +--- .../PeerCacheApplicationIntegrationTests.java | 19 +- .../data/gemfire/process/ProcessExecutor.java | 25 +- .../data/gemfire/process/ProcessWrapper.java | 127 +++--- .../gemfire/process/support/ProcessUtils.java | 27 +- .../ClientServerIntegrationTestsSupport.java | 154 ++++++- .../gemfire/util/ArrayUtilsUnitTests.java | 8 +- .../util/CollectionUtilsUnitTests.java | 6 +- src/test/resources/shiro.ini | 42 ++ src/test/resources/shiro.properties | 32 ++ template.mf | 15 +- 31 files changed, 1923 insertions(+), 237 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableOffHeap.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableRedisServer.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java create mode 100644 src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java create mode 100644 src/test/resources/shiro.ini create mode 100644 src/test/resources/shiro.properties diff --git a/build.gradle b/build.gradle index 5b3524df..4a12ffb2 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ apply plugin: "java" apply plugin: 'eclipse' apply plugin: 'idea' apply from: "$rootDir/maven.gradle" -apply plugin: 'bundlor' +//apply plugin: 'bundlor' apply plugin: 'propdeps' apply plugin: 'docbook-reference' apply plugin: 'org.asciidoctor.gradle.asciidoctor' @@ -101,6 +101,7 @@ dependencies { compile("io.pivotal.gemfire:geode-cq:$gemfireVersion") compile("io.pivotal.gemfire:geode-wan:$gemfireVersion") optional("com.google.code.findbugs:annotations:$googleCodeFindbugsVersion") + optional("org.apache.shiro:shiro-spring:$shiroVersion") runtime("antlr:antlr:$antlrVersion") optional "javax.enterprise:cdi-api:$cdiVersion" @@ -112,8 +113,8 @@ dependencies { testCompile("org.springframework:spring-test:$springVersion") { exclude group: "commons-logging", module: "commons-logging" } - testCompile "junit:junit:$junitVersion" testCompile "javax.annotation:jsr250-api:1.0", optional + testCompile "junit:junit:$junitVersion" testCompile "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion" testCompile "org.assertj:assertj-core:$assertjVersion" testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" @@ -132,9 +133,11 @@ dependencies { sharedResources "org.springframework.data.build:spring-data-build-resources:$springDataBuildVersion@zip" } +/* bundlor { manifestTemplate = file("template.mf").text } +*/ jar { manifest.attributes['Implementation-Title'] = project.name diff --git a/gradle.properties b/gradle.properties index fb4aa5fe..3f42d223 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,7 @@ multiThreadedtcVersion=1.01 openwebbeansVersion=1.2.8 servletApiVersion=2.5 slf4jVersion=1.7.21 +shiroVersion=1.3.1 spring.range="[5.0.0, 6.0.0)" springVersion=5.0.0.M3 springDataBuildVersion=2.0.0.BUILD-SNAPSHOT diff --git a/pom.xml b/pom.xml index 3e712979..9aaba126 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 2.7.7 + 1.3.1 2.5.0 9.0.0-beta.1 2.0.2 @@ -98,6 +99,13 @@ true + + org.apache.shiro + shiro-spring + ${apache-shiro.version} + true + + org.springframework.shell spring-shell diff --git a/src/main/asciidoc/appendix/appendix-schema.adoc b/src/main/asciidoc/appendix/appendix-schema.adoc index 34749a35..d49f85d4 100644 --- a/src/main/asciidoc/appendix/appendix-schema.adoc +++ b/src/main/asciidoc/appendix/appendix-schema.adoc @@ -7,13 +7,13 @@ [source,xml] ---- -include::{resourcesDir}/org/springframework/data/gemfire/config/spring-gemfire-1.8.xsd[] +include::{resourcesDir}/org/springframework/data/gemfire/config/spring-gemfire-2.0.xsd[] ---- == Spring Data GemFire Data Access Schema (gfe-data) [source,xml] ---- -include::{resourcesDir}/org/springframework/data/gemfire/config/spring-data-gemfire-1.8.xsd[] +include::{resourcesDir}/org/springframework/data/gemfire/config/spring-data-gemfire-2.0.xsd[] ---- diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java new file mode 100644 index 00000000..86ff7fe0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ApacheShiroSecurityConfiguration.java @@ -0,0 +1,253 @@ +/* + * 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.config.annotation; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.internal.security.SecurityService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.OrderComparator; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * The {@link ApacheShiroSecurityConfiguration} class is a Spring {@link Configuration @Configuration} component + * responsible for configuring and initializing the Apache Shiro security framework in order to secure Apache Geode + * administrative and data access operations. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.internal.security.SecurityService + * @see org.apache.shiro.mgt.DefaultSecurityManager + * @see org.apache.shiro.realm.Realm + * @see org.apache.shiro.spring.LifecycleBeanPostProcessor + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.ListableBeanFactory + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Condition + * @see org.springframework.context.annotation.Conditional + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.ApacheShiroSecurityConfiguration.ApacheShiroPresentCondition + * @since 1.9.0 + */ +@Configuration +@Conditional(ApacheShiroSecurityConfiguration.ApacheShiroPresentCondition.class) +@SuppressWarnings("unused") +public class ApacheShiroSecurityConfiguration implements BeanFactoryAware { + + private ListableBeanFactory beanFactory; + + /** + * @inheritDoc + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + Assert.isInstanceOf(ListableBeanFactory.class, beanFactory); + this.beanFactory = (ListableBeanFactory) beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory}. + * + * @return a reference to the Spring {@link BeanFactory}. + * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly initialized. + * @see org.springframework.beans.factory.BeanFactory + */ + protected ListableBeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); + return this.beanFactory; + } + + /** + * {@link Bean} definition to define, configure and register an Apache Shiro Spring + * {@link LifecycleBeanPostProcessor} to automatically call lifecycle callback methods + * on Shiro security components during Spring container initialization and destruction phases. + * + * @return an instance of the Apache Shiro Spring {@link LifecycleBeanPostProcessor} to handle the lifecycle + * of Apache Shiro security framework components. + * @see org.apache.shiro.spring.LifecycleBeanPostProcessor + */ + @Bean + public BeanPostProcessor shiroLifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * {@link Bean} definition to define, configure and register an Apache Shiro + * {@link org.apache.shiro.mgt.SecurityManager} implementation to secure Apache Geode. + * + * The registration of this {@link Bean} definition is dependent upon whether the user is using Apache Shiro + * to secure Apache Geode, which is determined by the presence of Apache Shiro {@link Realm Realms} + * declared in the Spring {@link org.springframework.context.ApplicationContext}. + * + * This {@link Bean} definition declares a dependency on the Apache Geode {@link GemFireCache} instance + * in order to ensure the Geode cache is created and initialized first. This ensures that any internal Geode + * security configuration logic is evaluated and processed before SDG attempts to configure Apache Shiro + * as Apache Geode's security provider. + * + * Additionally, this {@link Bean} definition will register the Apache Shiro + * {@link org.apache.geode.security.SecurityManager} with the Apache Shiro security framework + * + * Finally, this method proceeds to enable Apache Geode security. + + * @return an Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} implementation used to secure Apache Geode. + * @throws IllegalStateException if an Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} was registered + * with the Apache Shiro security framework but Apache Geode security could not be enabled. + * @see org.apache.shiro.mgt.SecurityManager + * @see #registerSecurityManager(org.apache.shiro.mgt.SecurityManager) + * @see #enableApacheGeodeSecurity() + * @see #resolveRealms() + * @see #registerSecurityManager(org.apache.shiro.mgt.SecurityManager) + * @see #enableApacheGeodeSecurity() + */ + @Bean + public org.apache.shiro.mgt.SecurityManager shiroSecurityManager(GemFireCache gemfireCache) { + org.apache.shiro.mgt.SecurityManager shiroSecurityManager = null; + + List realms = resolveRealms(); + + if (!realms.isEmpty()) { + shiroSecurityManager = registerSecurityManager(new DefaultSecurityManager(realms)); + + if (!enableApacheGeodeSecurity()) { + throw new IllegalStateException("Failed to enable security services in Apache Geode"); + } + } + + return shiroSecurityManager; + } + + /** + * Resolves all the Apache Shiro {@link Realm Realms} declared and configured as Spring managed beans + * in the Spring {@link org.springframework.context.ApplicationContext}. + * + * This method will order the Realms according to priority order to ensure that the Apache Shiro Realms + * are applied in the correct sequence, as declared/configured. + * + * @return a {@link List} of all Apache Shiro {@link Realm Realms} declared and configured as Spring managed beans + * in the Spring {@link org.springframework.context.ApplicationContext}. + * @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(Class, boolean, boolean) + * @see org.springframework.core.OrderComparator + * @see org.apache.shiro.realm.Realm + */ + protected List resolveRealms() { + try { + Map realmBeans = getBeanFactory().getBeansOfType(Realm.class, false, true); + List realms = new ArrayList<>(CollectionUtils.nullSafeMap(realmBeans).values()); + Collections.sort(realms, OrderComparator.INSTANCE); + return realms; + } + catch (Exception ignore) { + return Collections.emptyList(); + } + } + + /** + * Registers the given Apache Shiro {@link org.apache.shiro.mgt.SecurityManager} with the Apache Shiro + * security framework. + * + * @param securityManager {@link org.apache.shiro.mgt.SecurityManager} to register. + * @return the given {@link org.apache.shiro.mgt.SecurityManager} reference. + * @throws IllegalArgumentException if {@link org.apache.shiro.mgt.SecurityManager} is {@literal null}. + * @see org.apache.shiro.SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) + * @see org.apache.shiro.mgt.SecurityManager + */ + protected org.apache.shiro.mgt.SecurityManager registerSecurityManager( + org.apache.shiro.mgt.SecurityManager securityManager) { + + Assert.notNull(securityManager, "The Apache Shiro SecurityManager to register must not be null"); + + SecurityUtils.setSecurityManager(securityManager); + + return securityManager; + } + + /** + * Sets the Apache Geode, Integrated Security {@link SecurityService} property {@literal isIntegratedSecurity} + * to {@literal true} to indicate that Apache Geode security is enabled. + * + * @return a boolean value indicating whether Apache Geode's Integrated Security framework services + * were successfully enabled. + * @see org.apache.geode.internal.security.SecurityService#getSecurityService() + */ + protected boolean enableApacheGeodeSecurity() { + SecurityService securityService = SecurityService.getSecurityService(); + + if (securityService != null) { + String isIntegratedSecurityFieldName = "isIntegratedSecurity"; + + Field isIntegratedSecurity = ReflectionUtils.findField(securityService.getClass(), + isIntegratedSecurityFieldName, Boolean.class); + + isIntegratedSecurity = (isIntegratedSecurity != null ? isIntegratedSecurity + : ReflectionUtils.findField(securityService.getClass(), isIntegratedSecurityFieldName, Boolean.TYPE)); + + if (isIntegratedSecurity != null) { + ReflectionUtils.makeAccessible(isIntegratedSecurity); + ReflectionUtils.setField(isIntegratedSecurity, securityService, Boolean.TRUE); + + return true; + } + } + + return false; + } + + /** + * A Spring {@link Condition} to determine whether the user has included (declared) the 'shiro-spring' dependency + * on their application's classpath, which is necessary for configuring Apache Shiro to secure Apache Geode + * in a Spring context. + * + * @see org.springframework.context.annotation.Condition + */ + public static class ApacheShiroPresentCondition implements Condition { + + protected static final String APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESSOR_CLASS_NAME = + "org.apache.shiro.spring.LifecycleBeanPostProcessor"; + + /** + * @inheritDoc + */ + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return ClassUtils.isPresent(APACHE_SHIRO_LIFECYCLE_BEAN_POST_PROCESSOR_CLASS_NAME, + context.getClassLoader()); + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AuthConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AuthConfiguration.java index 1c27e15f..8984b717 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AuthConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AuthConfiguration.java @@ -40,43 +40,59 @@ public class AuthConfiguration extends EmbeddedServiceConfigurationSupport { public static final String DEFAULT_SECURITY_LOG_LEVEL = "config"; - /* (non-Javadoc) */ + protected static final String GEMFIRE_SECURITY_PROPERTY_FILE = "gemfireSecurityPropertyFile"; + protected static final String SECURITY_CLIENT_ACCESSOR = "security-client-accessor"; + protected static final String SECURITY_CLIENT_ACCESSOR_POST_PROCESSOR = "security-client-accessor-pp"; + protected static final String SECURITY_CLIENT_AUTH_INIT = "security-client-auth-init"; + protected static final String SECURITY_CLIENT_AUTHENTICATOR = "security-client-authenticator"; + protected static final String SECURITY_CLIENT_DIFFIE_HELLMAN_ALGORITHM = "security-client-dhalgo"; + protected static final String SECURITY_LOG_FILE = "security-log-file"; + protected static final String SECURITY_LOG_LEVEL = "security-log-level"; + protected static final String SECURITY_PEER_AUTH_INIT = "security-peer-auth-init"; + protected static final String SECURITY_PEER_AUTHENTICATOR = "security-peer-authenticator"; + protected static final String SECURITY_PEER_VERIFY_MEMBER_TIMEOUT = "security-peer-verifymember-timeout"; + + /** + * @inheritDoc + */ @Override protected Class getAnnotationType() { return EnableAuth.class; } - /* (non-Javadoc) */ + /** + * @inheritDoc + */ @Override protected Properties toGemFireProperties(Map annotationAttributes) { PropertiesBuilder gemfireProperties = PropertiesBuilder.create(); - gemfireProperties.setProperty("gemfireSecurityPropertyFile", annotationAttributes.get("securityPropertiesFile")); + gemfireProperties.setProperty(GEMFIRE_SECURITY_PROPERTY_FILE, annotationAttributes.get("securityPropertiesFile")); - gemfireProperties.setProperty("security-client-accessor", annotationAttributes.get("clientAccessor")); + gemfireProperties.setProperty(SECURITY_CLIENT_ACCESSOR, annotationAttributes.get("clientAccessor")); - gemfireProperties.setProperty("security-client-accessor-pp", + gemfireProperties.setProperty(SECURITY_CLIENT_ACCESSOR_POST_PROCESSOR, annotationAttributes.get("clientAccessPostOperation")); - gemfireProperties.setProperty("security-client-auth-init", + gemfireProperties.setProperty(SECURITY_CLIENT_AUTH_INIT, annotationAttributes.get("clientAuthenticationInitializer")); - gemfireProperties.setProperty("security-client-authenticator", annotationAttributes.get("clientAuthenticator")); + gemfireProperties.setProperty(SECURITY_CLIENT_AUTHENTICATOR, annotationAttributes.get("clientAuthenticator")); - gemfireProperties.setProperty("security-client-dhalgo", + gemfireProperties.setProperty(SECURITY_CLIENT_DIFFIE_HELLMAN_ALGORITHM, annotationAttributes.get("clientDiffieHellmanAlgorithm")); - gemfireProperties.setProperty("security-peer-auth-init", + gemfireProperties.setProperty(SECURITY_PEER_AUTH_INIT, annotationAttributes.get("peerAuthenticationInitializer")); - gemfireProperties.setProperty("security-peer-authenticator", annotationAttributes.get("peerAuthenticator")); + gemfireProperties.setProperty(SECURITY_PEER_AUTHENTICATOR, annotationAttributes.get("peerAuthenticator")); - gemfireProperties.setPropertyIfNotDefault("security-peer-verifymember-timeout", + gemfireProperties.setPropertyIfNotDefault(SECURITY_PEER_VERIFY_MEMBER_TIMEOUT, annotationAttributes.get("peerVerifyMemberTimeout"), DEFAULT_PEER_VERIFY_MEMBER_TIMEOUT); - gemfireProperties.setProperty("security-log-file", annotationAttributes.get("securityLogFile")); + gemfireProperties.setProperty(SECURITY_LOG_FILE, annotationAttributes.get("securityLogFile")); - gemfireProperties.setPropertyIfNotDefault("security-log-level", + gemfireProperties.setPropertyIfNotDefault(SECURITY_LOG_LEVEL, annotationAttributes.get("securityLogLevel"), DEFAULT_SECURITY_LOG_LEVEL); return gemfireProperties.build(); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableAuth.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableAuth.java index 2c7335bf..3e127222 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableAuth.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableAuth.java @@ -34,10 +34,11 @@ import org.springframework.context.annotation.Import; * annotated class to configure and enable GemFire/Geode's Authentication and Authorization framework services. * * @author John Blum - * @see org.springframework.data.gemfire.config.annotation.AuthConfiguration * @see org.apache.geode.security.AccessControl * @see org.apache.geode.security.AuthInitialize * @see org.apache.geode.security.Authenticator + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.AuthConfiguration * @see Authentication * @see Authorization * @since 1.9.0 diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableOffHeap.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableOffHeap.java new file mode 100644 index 00000000..f69eb6b2 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableOffHeap.java @@ -0,0 +1,67 @@ +/* + * 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.config.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.springframework.context.annotation.Import; + +/** + * The EnableOffHeap annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * annotated class to configure and enable Apache Geode Off-Heap Memory support and data storage + * in Geode's cache {@link org.apache.geode.cache.Region Regions}. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.OffHeapConfiguration + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(OffHeapConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableOffHeap { + + /** + * Specifies the size of off-heap memory in megabytes (m) or gigabytes (g). For example: + * + *
+	 *     
+	 *       off-heap-memory-size=4096m
+	 *       off-heap-memory-size=120g
+	 *     
+	 * 
+ * + * Defaults to unset. + */ + String memorySize(); + + /** + * Idenfitied the Regions by name in which the off-heap memory setting will be applied. + * + * Defaults to all Regions. + */ + String[] regionNames() default {}; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableRedisServer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableRedisServer.java new file mode 100644 index 00000000..11ad2f72 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableRedisServer.java @@ -0,0 +1,62 @@ +/* + * 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.config.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.springframework.context.annotation.Import; + +/** + * The EnableRedisServer annotation marks a Spring {@link org.springframework.context.annotation.Configuration} + * class to embed the Redis service in the GemFire server-side data member node. + * + * The Redis service implements the Redis server protocol enabling Redis clients to connect and speak + * to Pivotal GemFire or Apache Geode. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.MemcachedServerConfiguration + * @since 1.9.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(RedisServerConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableRedisServer { + + /** + * Configures the Network bind-address on which the Redis server will accept connections. + * + * Defaults to {@literal localhost}. + */ + String bindAddress() default ""; + + /** + * Configures the Network port on which the Redis server will listen for Redis client connections. + * + * Defaults to {@literal 6379}. + */ + int port() default RedisServerConfiguration.DEFAULT_REDIS_PORT; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java new file mode 100644 index 00000000..d1787d5d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableSecurity.java @@ -0,0 +1,115 @@ +/* + * 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.config.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.security.AuthInitialize; +import org.springframework.context.annotation.Import; + +/** + * The {@link EnableSecurity} annotation marks a Spring {@link org.springframework.context.annotation.Configuration} + * annotated class to configure and enable Apache Geode's Security features for authentication, authorization + * and post processing. + * + * @author John Blum + * @see GeodeIntegratedSecurityConfiguration + * @see org.apache.geode.security.AuthInitialize + * @see org.apache.geode.security.SecurityManager + * @see org.apache.geode.security.PostProcessor + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.ApacheShiroSecurityConfiguration + * @see org.springframework.data.gemfire.config.annotation.GeodeIntegratedSecurityConfiguration + * @since 1.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import({ ApacheShiroSecurityConfiguration.class, GeodeIntegratedSecurityConfiguration.class }) +@SuppressWarnings({ "unused" }) +public @interface EnableSecurity { + + /** + * Used for authentication. Static creation method returning an {@link AuthInitialize} object, + * which obtains credentials for clients. + * + * Defaults to unset. + */ + String clientAuthenticationInitializer() default ""; + + /** + * Used with authentication. Static creation method returning an {@link AuthInitialize} object, which obtains + * credentials for peers in a distributed system. + * + * Defaults to unset. + */ + String peerAuthenticationInitializer() default ""; + + /** + * Specifies the application {@link Class} type implementing the Apache Geode + * {@link org.apache.geode.security.SecurityManager} interface to enable security in Apache Geode. + * + * Defaults to {@link Void}. + */ + Class securityManagerClass() default Void.class; + + /** + * Specifies the fully-qualified class name of the application {@link Class} implementing the Apache Geode + * {@link org.apache.geode.security.SecurityManager} interface to enable security in Apache Geode. + * + * Use this Annotation attribute if you are uncertain whether the application class is on the classpath or not. + * + * Default is unset. + */ + String securityManagerClassName() default ""; + + /** + * Specifies the application {@link Class} type implementing the Apache Geode + * {@link org.apache.geode.security.PostProcessor} interface, which used to transform sensitive data + * returned from secure data access operations. + * + * Defaults to {@link Void}. + */ + Class securityPostProcessorClass() default Void.class; + + /** + * Specifies the fully-qualified class name of the application {@link Class} implementing the Apache Geode + * {@link org.apache.geode.security.PostProcessor} interface, which used to transform sensitive data + * returned from secure data access operations. + * + * Use this Annotation attribute if you are uncertain whether the application class is on the classpath or not. + * + * Default is unset. + */ + String securityPostProcessorClassName() default ""; + + /** + * Sets the Geode System Property referring to the location of an Apache Shiro INI file used to configure + * the Apache Shiro Security Framework to secure Apache Geode. + * + * Default is unset. + */ + String shiroIniResourcePath() default ""; + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java new file mode 100644 index 00000000..7d8d4fa0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/GeodeIntegratedSecurityConfiguration.java @@ -0,0 +1,98 @@ +/* + * 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.config.annotation; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; +import org.springframework.data.gemfire.util.PropertiesBuilder; + +/** + * The {@link GeodeIntegratedSecurityConfiguration} class is a {@link EmbeddedServiceConfigurationSupport} implementation + * that enables Apache Geode's Integrated Security framework and services. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport + * @since 1.0.0 + */ +@SuppressWarnings("unused") +public class GeodeIntegratedSecurityConfiguration extends EmbeddedServiceConfigurationSupport { + + protected static final String SECURITY_CLIENT_AUTH_INIT = "security-client-auth-init"; + protected static final String SECURITY_MANAGER = "security-manager"; + protected static final String SECURITY_PEER_AUTH_INIT = "security-peer-auth-init"; + protected static final String SECURITY_POST_PROCESSOR = "security-post-processor"; + protected static final String SECURITY_SHIRO_INIT = "security-shiro-init"; + + /** + * @inheritDoc + */ + @Override + protected Class getAnnotationType() { + return EnableSecurity.class; + } + + /* (non-Javadoc) */ + protected boolean isShiroSecurityConfigured() { + try { + // NOTE experimental... + //return resolveBean(ApacheShiroSecurityConfiguration.class).isRealmsPresent(); + return false; + } + catch (Exception ignore) { + return false; + } + } + + /* (non-Javadoc) */ + protected boolean isShiroSecurityNotConfigured() { + return !isShiroSecurityConfigured(); + } + + /** + * @inheritDoc + */ + @Override + protected Properties toGemFireProperties(Map annotationAttributes) { + PropertiesBuilder gemfireProperties = new PropertiesBuilder(); + + gemfireProperties.setProperty(SECURITY_CLIENT_AUTH_INIT, + annotationAttributes.get("clientAuthenticationInitializer")); + + if (isShiroSecurityNotConfigured()) { + gemfireProperties.setPropertyIfNotDefault(SECURITY_MANAGER, + annotationAttributes.get("securityManagerClass"), Void.class); + + gemfireProperties.setProperty(SECURITY_MANAGER, annotationAttributes.get("securityManagerClassName")); + + gemfireProperties.setProperty(SECURITY_SHIRO_INIT, annotationAttributes.get("shiroIniResourcePath")); + } + + gemfireProperties.setProperty(SECURITY_PEER_AUTH_INIT, + annotationAttributes.get("peerAuthenticationInitializer")); + + gemfireProperties.setPropertyIfNotDefault(SECURITY_POST_PROCESSOR, + annotationAttributes.get("securityPostProcessorClass"), Void.class); + + gemfireProperties.setProperty(SECURITY_POST_PROCESSOR, + annotationAttributes.get("securityPostProcessorClassName")); + + return gemfireProperties.build(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java new file mode 100644 index 00000000..f0854c07 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/OffHeapConfiguration.java @@ -0,0 +1,156 @@ +/* + * 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.config.annotation; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.geode.cache.Region; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.GenericRegionFactoryBean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.data.gemfire.util.PropertiesBuilder; + +/** + * The OffHeapConfiguration class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} + * capable of enabling GemFire cache {@link Region Regions} to use Off-Heap memory for data storage. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.EnableOffHeap + * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport + * @since 1.9.0 + */ +public class OffHeapConfiguration extends EmbeddedServiceConfigurationSupport { + + /* (non-Javadoc) */ + @Override + protected Class getAnnotationType() { + return EnableOffHeap.class; + } + + /* (non-Javadoc) */ + @Override + protected void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + Map annotationAttributes, BeanDefinitionRegistry registry) { + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( + OffHeapBeanFactoryPostProcessor.class); + + builder.addConstructorArgValue(annotationAttributes.get("regionNames")); + + registry.registerBeanDefinition(generateBeanName(OffHeapBeanFactoryPostProcessor.class), + builder.getBeanDefinition()); + } + + /* (non-Javadoc) */ + @Override + protected Properties toGemFireProperties(Map annotationAttributes) { + PropertiesBuilder gemfireProperties = PropertiesBuilder.create(); + + gemfireProperties.setProperty("off-heap-memory-size", annotationAttributes.get("memorySize")); + + return gemfireProperties.build(); + } + + @SuppressWarnings("unused") + protected static class OffHeapBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + protected static final Set REGION_FACTORY_BEAN_TYPES = new HashSet(5); + + static { + REGION_FACTORY_BEAN_TYPES.add(ClientRegionFactoryBean.class.getName()); + REGION_FACTORY_BEAN_TYPES.add(GenericRegionFactoryBean.class.getName()); + REGION_FACTORY_BEAN_TYPES.add(LocalRegionFactoryBean.class.getName()); + REGION_FACTORY_BEAN_TYPES.add(PartitionedRegionFactoryBean.class.getName()); + REGION_FACTORY_BEAN_TYPES.add(ReplicatedRegionFactoryBean.class.getName()); + } + + private final Set regionNames; + + protected OffHeapBeanFactoryPostProcessor(String[] regionNames) { + this(CollectionUtils.asSet(ArrayUtils.nullSafeArray(regionNames, String.class))); + } + + protected OffHeapBeanFactoryPostProcessor(Set regionNames) { + this.regionNames = CollectionUtils.nullSafeSet(regionNames); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + for (String beanName : beanFactory.getBeanDefinitionNames()) { + BeanDefinition bean = beanFactory.getBeanDefinition(beanName); + + if (isTargetedRegionBean(beanName, bean, beanFactory)) { + bean.getPropertyValues().addPropertyValue("offHeap", true); + } + } + } + + boolean isTargetedRegionBean(String beanName, BeanDefinition bean, + ConfigurableListableBeanFactory beanFactory) { + + return (isRegionBean(bean) && isNamedRegion(beanName, bean, beanFactory)); + } + + boolean isRegionBean(BeanDefinition bean) { + return (bean != null && REGION_FACTORY_BEAN_TYPES.contains(bean.getBeanClassName())); + } + + boolean isNamedRegion(String beanName, BeanDefinition bean, ConfigurableListableBeanFactory beanFactory) { + return (CollectionUtils.isEmpty(regionNames) || CollectionUtils.containsAny(regionNames, + getBeanNames(beanName, bean, beanFactory))); + } + + Collection getBeanNames(String beanName, BeanDefinition bean, BeanFactory beanFactory) { + Collection beanNames = new HashSet(); + + beanNames.add(beanName); + Collections.addAll(beanNames, beanFactory.getAliases(beanName)); + + PropertyValue regionName = bean.getPropertyValues().getPropertyValue("regionName"); + + if (regionName != null) { + Object regionNameValue = regionName.getValue(); + + if (regionNameValue != null) { + beanNames.add(regionNameValue.toString()); + } + } + + return beanNames; + } + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java new file mode 100644 index 00000000..949dacf0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java @@ -0,0 +1,52 @@ +/* + * 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.config.annotation; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport; +import org.springframework.data.gemfire.util.PropertiesBuilder; + +/** + * The RedisServerConfiguration class is a Spring {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar} + * that applies additional GemFire configuration by way of GemFire System properties to configure + * an embedded Redis server. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.EnableRedisServer + * @see org.springframework.data.gemfire.config.annotation.support.EmbeddedServiceConfigurationSupport + * @since 1.9.0 + */ +public class RedisServerConfiguration extends EmbeddedServiceConfigurationSupport { + + protected static final int DEFAULT_REDIS_PORT = 6379; + + @Override + protected Class getAnnotationType() { + return EnableRedisServer.class; + } + + @Override + protected Properties toGemFireProperties(Map annotationAttributes) { + return new PropertiesBuilder() + .setProperty("redis-bind-address", annotationAttributes.get("bindAddress")) + .setProperty("redis-port", resolvePort((Integer)annotationAttributes.get("port"), DEFAULT_REDIS_PORT)) + .build(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java index 50b505c6..4d20b2e0 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CollectionUtils.java @@ -24,8 +24,8 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; +import java.util.TreeMap; import org.springframework.util.Assert; @@ -53,8 +53,9 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @param elements array of objects to add to the {@link Set}. * @return an unmodifiable {@link Set} containing the elements from the given object array. */ + @SafeVarargs public static Set asSet(T... elements) { - Set set = new HashSet(elements.length); + Set set = new HashSet<>(elements.length); Collections.addAll(set, elements); return Collections.unmodifiableSet(set); } @@ -68,12 +69,8 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.lang.Iterable * @see java.util.Enumeration */ - public static Iterable iterable(final Enumeration enumeration) { - return new Iterable() { - @Override public Iterator iterator() { - return org.springframework.util.CollectionUtils.toIterator(enumeration); - } - }; + public static Iterable iterable(Enumeration enumeration) { + return () -> org.springframework.util.CollectionUtils.toIterator(enumeration); } /** @@ -85,12 +82,8 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.lang.Iterable * @see java.util.Iterator */ - public static Iterable iterable(final Iterator iterator) { - return new Iterable() { - @Override public Iterator iterator() { - return iterator; - } - }; + public static Iterable iterable(Iterator iterator) { + return () -> iterator; } /** @@ -105,7 +98,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.Collection */ public static Collection nullSafeCollection(Collection collection) { - return (collection != null ? collection : Collections.emptyList()); + return (collection != null ? collection : Collections.emptyList()); } /** @@ -119,23 +112,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.Iterator */ public static Iterable nullSafeIterable(Iterable iterable) { - return (iterable != null ? iterable : new Iterable() { - @Override public Iterator iterator() { - return new Iterator() { - @Override public boolean hasNext() { - return false; - } - - @Override public T next() { - throw new NoSuchElementException("No more elements"); - } - - @Override public void remove() { - throw new UnsupportedOperationException("Operation not supported"); - } - }; - } - }); + return (iterable != null ? iterable : Collections::emptyIterator); } /** @@ -149,7 +126,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.List */ public static List nullSafeList(List list) { - return (list != null ? list : Collections.emptyList()); + return (list != null ? list : Collections.emptyList()); } /** @@ -164,7 +141,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.Map */ public static Map nullSafeMap(Map map) { - return (map != null ? map : Collections.emptyMap()); + return (map != null ? map : Collections.emptyMap()); } /** @@ -178,7 +155,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio * @see java.util.Set */ public static Set nullSafeSet(Set set) { - return (set != null ? set : Collections.emptySet()); + return (set != null ? set : Collections.emptySet()); } /** @@ -211,7 +188,7 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio public static List subList(List source, int... indices) { Assert.notNull(source, "List must not be null"); - List result = new ArrayList(indices.length); + List result = new ArrayList<>(indices.length); for (int index : indices) { result.add(source.get(index)); @@ -219,4 +196,23 @@ public abstract class CollectionUtils extends org.springframework.util.Collectio return result; } + + /* (non-Javadoc) */ + public static String toString(Map map) { + StringBuilder builder = new StringBuilder("{\n"); + + int count = 0; + + for (Map.Entry entry : new TreeMap<>(map).entrySet()) { + builder.append(++count > 1 ? ",\n" : ""); + builder.append("\t"); + builder.append(entry.getKey()); + builder.append(" = "); + builder.append(entry.getValue()); + } + + builder.append("\n}"); + + return builder.toString(); + } } diff --git a/src/main/java/org/springframework/data/gemfire/util/DistributedSystemUtils.java b/src/main/java/org/springframework/data/gemfire/util/DistributedSystemUtils.java index 4318bf2a..f950b6bf 100644 --- a/src/main/java/org/springframework/data/gemfire/util/DistributedSystemUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/DistributedSystemUtils.java @@ -46,6 +46,8 @@ public abstract class DistributedSystemUtils extends SpringUtils { public static final String DURABLE_CLIENT_ID_PROPERTY_NAME = DistributionConfig.DURABLE_CLIENT_ID_NAME; public static final String DURABLE_CLIENT_TIMEOUT_PROPERTY_NAME = DistributionConfig.DURABLE_CLIENT_TIMEOUT_NAME; + public static final String GEMFIRE_PREFIX = DistributionConfig.GEMFIRE_PREFIX; + public static final String NAME_PROPERTY_NAME = DistributionConfig.NAME_NAME; /* (non-Javadoc) */ public static Properties configureDurableClient(Properties gemfireProperties, @@ -85,5 +87,4 @@ public abstract class DistributedSystemUtils extends SpringUtils { public static T getDistributedSystem(GemFireCache gemfireCache) { return (gemfireCache != null ? (T) gemfireCache.getDistributedSystem() : null); } - } diff --git a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java index 403b8f5e..21e1f3b5 100644 --- a/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationTests.java @@ -102,9 +102,9 @@ public class GemfireTemplateQueriesOnGroupedPooledClientCacheRegionsIntegrationT @BeforeClass public static void setupGemFireCluster() throws Exception { serverOne = ProcessExecutor.launch(createDirectory("serverOne"), GemFireCacheServerOneConfiguration.class); - waitForCacheServerToStart("localhost", 41414); + waitForServerToStart("localhost", 41414); serverTwo = ProcessExecutor.launch(createDirectory("serverTwo"), GemFireCacheServerTwoConfiguration.class); - waitForCacheServerToStart("localhost", 42424); + waitForServerToStart("localhost", 42424); } @AfterClass diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..076bb6b9 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractGeodeSecurityIntegrationTests.java @@ -0,0 +1,404 @@ +/* + * 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.io.Serializable; +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.geode.LogWriter; +import org.apache.geode.cache.CacheLoader; +import org.apache.geode.cache.CacheLoaderException; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.LoaderHelper; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.apache.geode.cache.client.ServerOperationException; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.security.AuthInitialize; +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.NotAuthorizedException; +import org.apache.geode.security.ResourcePermission; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runners.MethodSorters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.data.gemfire.util.PropertiesBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Abstract base test class for implementing Apache Geode Integrated Security Integration Tests. + * + * @author John Blum + * @see org.junit.FixMethodOrder + * @see org.junit.Test + * @see lombok + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport + * @since 1.0.0 + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@SuppressWarnings("unused") +public abstract class AbstractGeodeSecurityIntegrationTests extends ClientServerIntegrationTestsSupport { + + protected static final int CACHE_SERVER_PORT = 13531; + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractGeodeSecurityIntegrationTests.class); + + protected static final String CACHE_SERVER_HOST = "localhost"; + protected static final String GEODE_SECURITY_PROFILE_PROPERTY = "geode.security.profile"; + protected static final String SECURITY_PASSWORD_PROPERTY = "security-password"; + protected static final String SECURITY_USERNAME_PROPERTY = "security-username"; + + private static final AtomicInteger RUN_COUNT = new AtomicInteger(0); + + private static ProcessWrapper geodeServerProcess; + + @BeforeClass + public static void runGeodeServer() throws IOException { + String geodeSecurityProfile = System.getProperty(GEODE_SECURITY_PROFILE_PROPERTY); + + if (StringUtils.hasText(geodeSecurityProfile)) { + runGeodeServer(geodeSecurityProfile); + } + } + + protected static ProcessWrapper runGeodeServer(String geodeSecurityProfile) throws IOException { + Assert.hasText(geodeSecurityProfile, String.format("The '%s' System property must be set", + GEODE_SECURITY_PROFILE_PROPERTY)); + + geodeServerProcess = run(GeodeServerConfiguration.class, + String.format("-Dgemfire.log-file=%s", logFile()), + String.format("-Dgemfire.log-level=%s", logLevel(TEST_GEMFIRE_LOG_LEVEL)), + String.format("-Dspring.profiles.active=apache-geode-server,%s", geodeSecurityProfile)); + + waitForServerToStart(CACHE_SERVER_HOST, CACHE_SERVER_PORT); + + return geodeServerProcess; + } + + @AfterClass + public static void stopGeodeServer() { + System.clearProperty(GEODE_SECURITY_PROFILE_PROPERTY); + stop(geodeServerProcess); + } + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Resource(name = "Echo") + private Region echo; + + @Test + @DirtiesContext + public void authorizedUser() { + assertThat(echo.get("one")).isEqualTo("one"); + assertThat(echo.put("two", "four")).isNull(); + assertThat(echo.get("two")).isEqualTo("four"); + } + + @Test + public void unauthorizedUser() { + assertThat(echo.get("one")).isEqualTo("one"); + + exception.expect(ServerOperationException.class); + exception.expectCause(is(instanceOf(NotAuthorizedException.class))); + exception.expectMessage(containsString("analyst not authorized for DATA:WRITE:Echo:two")); + + echo.put("two", "four"); + } + + protected static abstract class AuthInitializeSupport implements AuthInitialize { + + /** + * @inheritDoc + */ + @Override + public void init(LogWriter systemLogger, LogWriter securityLogger) throws AuthenticationFailedException { + } + + /** + * @inheritDoc + */ + @Override + public Properties getCredentials(Properties securityProperties, DistributedMember server, boolean isPeer) + throws AuthenticationFailedException { + + return getCredentials(securityProperties); + } + + /** + * @inheritDoc + */ + @Override + public void close() { + } + } + + public static class GeodeClientAuthInitialize extends AuthInitializeSupport { + + protected static final User ANALYST = User.newUser("analyst").with("p@55w0rd"); + protected static final User SCIENTIST = User.newUser("scientist").with("w0rk!ng4u"); + + private final User user; + + /* (non-Javadoc) */ + public static GeodeClientAuthInitialize create() { + return new GeodeClientAuthInitialize(RUN_COUNT.incrementAndGet() < 2 ? SCIENTIST : ANALYST); + } + + /* (non-Javadoc) */ + public GeodeClientAuthInitialize(User user) { + Assert.notNull(user, "User cannot be null"); + this.user = user; + } + + /** + * @inheritDoc + */ + @Override + public Properties getCredentials(Properties securityProperties) { + User user = getUser(); + + return new PropertiesBuilder() + .setProperty(SECURITY_USERNAME_PROPERTY, user.getName()) + .setProperty(SECURITY_PASSWORD_PROPERTY, user.getCredentials()) + .build(); + } + + /* (non-Javadoc) */ + protected User getUser() { + return this.user; + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + User user = getUser(); + return String.format("%1$s:%2$s", user.getName(), user.getCredentials()); + } + } + + @ClientCacheApplication(name = "GeodeSecurityIntegrationTestsClient", logLevel = TEST_GEMFIRE_LOG_LEVEL, + servers = { @ClientCacheApplication.Server(port = CACHE_SERVER_PORT) }) + @EnableAuth(clientAuthenticationInitializer = "org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests$GeodeClientAuthInitialize.create") + @Profile("apache-geode-client") + static class GeodeClientConfiguration { + + @Bean("Echo") + ClientRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + ClientRegionFactoryBean echoRegion = new ClientRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setClose(false); + echoRegion.setShortcut(ClientRegionShortcut.PROXY); + + return echoRegion; + } + } + + @CacheServerApplication(name = "GeodeSecurityIntegrationTestsServer", logLevel = TEST_GEMFIRE_LOG_LEVEL, + port = CACHE_SERVER_PORT) + @Import({ + ApacheShiroIniGeodeSecurityIntegrationTests.ApacheShiroIniConfiguration.class, + ApacheShiroRealmGeodeSecurityIntegrationTests.ApacheShiroRealmConfiguration.class, + ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.ApacheGeodeSecurityManagerConfiguration.class + }) + @Profile("apache-geode-server") + public static class GeodeServerConfiguration { + + public static void main(String[] args) { + runSpringApplication(GeodeServerConfiguration.class, args); + } + + @Autowired + private GemFireCache gemfireCache; + + @Bean("Echo") + LocalRegionFactoryBean echoRegion(GemFireCache gemfireCache) { + LocalRegionFactoryBean echoRegion = new LocalRegionFactoryBean<>(); + + echoRegion.setCache(gemfireCache); + echoRegion.setCacheLoader(echoCacheLoader()); + echoRegion.setClose(false); + echoRegion.setPersistent(false); + + return echoRegion; + } + + CacheLoader echoCacheLoader() { + return new CacheLoader() { + @Override + public String load(LoaderHelper helper) throws CacheLoaderException { + return helper.getKey(); + } + + @Override + public void close() { + } + }; + } + + @PostConstruct + public void postProcess() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Geode Distributed System Properties [{}]", + CollectionUtils.toString(gemfireCache.getDistributedSystem().getProperties())); + } + } + } + + @Getter + @EqualsAndHashCode(of = { "name", "credentials" }) + @RequiredArgsConstructor(staticName = "newUser") + @SuppressWarnings("all") + public static class User implements Iterable, Principal, Serializable { + + private Set roles = new HashSet<>(); + + @NonNull + private String name; + private String credentials; + + /* (non-Javadoc) */ + public boolean hasPermission(ResourcePermission permission) { + for (Role role : this) { + if (role.hasPermission(permission)) { + return true; + } + } + + return false; + } + + /* (non-Javadoc) */ + public boolean hasRole(Role role) { + return this.roles.contains(role); + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.roles).iterator(); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return getName(); + } + + /* (non-Javadoc) */ + public User with(String credentials) { + this.credentials = credentials; + return this; + } + + /* (non-Javadoc) */ + public User with(Role... roles) { + Collections.addAll(this.roles, roles); + return this; + } + } + + @Getter + @EqualsAndHashCode(of = "name") + @RequiredArgsConstructor(staticName = "newRole") + @SuppressWarnings("unsed") + public static class Role implements Iterable, Serializable { + + @NonNull + private String name; + + private Set permissions = new HashSet<>(); + + /* (non-Javadoc) */ + public boolean hasPermission(ResourcePermission permission) { + for (ResourcePermission thisPermission : this) { + if (thisPermission.implies(permission)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.permissions).iterator(); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return getName(); + } + + /* (non-Javadoc) */ + public Role with(ResourcePermission... permissions) { + Collections.addAll(this.permissions, permissions); + return this; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..29894639 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests.java @@ -0,0 +1,144 @@ +/* + * 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.config.annotation; + +import static org.apache.geode.security.ResourcePermission.Operation; +import static org.apache.geode.security.ResourcePermission.Resource; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.ResourcePermission; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an application-specific, Apache Geode + * {@link org.apache.geode.security.SecurityManager}. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE = + "geode-security-manager-property-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity(securityManagerClassName = "org.springframework.data.gemfire.config.annotation.ApacheGeodeSecurityManagerGeodeSecurityIntegrationTests$TestGeodeSecurityManager") + @Profile(GEODE_SECURITY_MANAGER_PROPERTY_CONFIGURATION_PROFILE) + public static class ApacheGeodeSecurityManagerConfiguration { + } + + protected interface GeodeSecurityRepository { + + ResourcePermission CLUSTER_MANAGE = new ResourcePermission(Resource.CLUSTER, Operation.MANAGE); + ResourcePermission CLUSTER_READ = new ResourcePermission(Resource.CLUSTER, Operation.READ); + ResourcePermission CLUSTER_WRITE = new ResourcePermission(Resource.CLUSTER, Operation.WRITE); + + ResourcePermission DATA_MANAGE = new ResourcePermission(Resource.DATA, Operation.MANAGE); + ResourcePermission DATA_READ = new ResourcePermission(Resource.DATA, Operation.READ); + ResourcePermission DATA_WRITE = new ResourcePermission(Resource.DATA, Operation.WRITE); + + Role ADMIN = Role.newRole("ADMIN").with(CLUSTER_MANAGE, CLUSTER_READ, CLUSTER_WRITE); + Role DBA = Role.newRole("DBA").with(DATA_MANAGE, DATA_READ, DATA_WRITE); + Role DATA_SCIENTIST = Role.newRole("DATA_SCIENTIST").with(DATA_READ, DATA_WRITE); + Role DATA_ANALYST = Role.newRole("DATA_ANALYST").with(DATA_READ); + Role GUEST = Role.newRole("GUEST"); + + User root = User.newUser("root").with("s3cr3t!").with(ADMIN, DBA); + User scientist = User.newUser("scientist").with("w0rk!ng4u").with(DATA_SCIENTIST); + User analyst = User.newUser("analyst").with("p@55w0rd").with(DATA_ANALYST); + User guest = User.newUser("guest").with("guest").with(GUEST); + + Set users = new HashSet<>(Arrays.asList(root, scientist, analyst, guest)); + + default User findBy(String username) { + return users.stream().filter((user) -> user.getName().equals(username)).findFirst().get(); + } + } + + public static abstract class GeodeSecurityManagerSupport implements org.apache.geode.security.SecurityManager { + + /** + * @inheritDoc + */ + @Override + public void init(Properties securityProps) { + } + + /** + * @inheritDoc + */ + @Override + public void close() { + } + } + + public static class TestGeodeSecurityManager extends GeodeSecurityManagerSupport { + + private final GeodeSecurityRepository securityRepository; + + public TestGeodeSecurityManager() { + this.securityRepository = new GeodeSecurityRepository() { }; + } + + /** + * @inheritDoc + */ + @Override + public Object authenticate(Properties credentials) throws AuthenticationFailedException { + String username = credentials.getProperty(SECURITY_USERNAME_PROPERTY); + String password = credentials.getProperty(SECURITY_PASSWORD_PROPERTY); + + User user = securityRepository.findBy(username); + + if (user == null || !user.getCredentials().equals(password)) { + throw new AuthenticationFailedException(String.format("Failed to authenticate user [%s]", username)); + } + + return user; + } + + /** + * @inheritDoc + */ + @Override + public boolean authorize(Object principal, ResourcePermission permission) { + return (principal instanceof User && ((User) principal).hasPermission(permission)); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..e196de15 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroIniGeodeSecurityIntegrationTests.java @@ -0,0 +1,54 @@ +/* + * 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.config.annotation; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an Apache Shiro INI security configuration resource. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheShiroIniGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String SHIRO_INI_CONFIGURATION_PROFILE = "shiro-ini-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(SHIRO_INI_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity(shiroIniResourcePath = "shiro.ini") + @Profile(SHIRO_INI_CONFIGURATION_PROFILE) + public static class ApacheShiroIniConfiguration { + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java new file mode 100644 index 00000000..bf667395 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ApacheShiroRealmGeodeSecurityIntegrationTests.java @@ -0,0 +1,67 @@ +/* + * 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.config.annotation; + +import java.io.IOException; + +import org.apache.geode.internal.security.shiro.GeodePermissionResolver; +import org.apache.shiro.realm.text.PropertiesRealm; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for Apache Geode Integrated Security using an Apache Shiro Realm security configuration + * as a Spring managed bean in a Spring {@link org.springframework.context.ApplicationContext}. + * + * @author John Blum + * @see org.springframework.data.gemfire.config.annotation.AbstractGeodeSecurityIntegrationTests + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = AbstractGeodeSecurityIntegrationTests.GeodeClientConfiguration.class) +@ActiveProfiles("apache-geode-client") +public class ApacheShiroRealmGeodeSecurityIntegrationTests extends AbstractGeodeSecurityIntegrationTests { + + protected static final String SHIRO_REALM_CONFIGURATION_PROFILE = "shiro-realm-configuration"; + + @BeforeClass + public static void setup() throws IOException { + runGeodeServer(SHIRO_REALM_CONFIGURATION_PROFILE); + } + + @Configuration + @EnableSecurity + @Profile(SHIRO_REALM_CONFIGURATION_PROFILE) + @SuppressWarnings("unused") + public static class ApacheShiroRealmConfiguration { + + @Bean + public PropertiesRealm shiroRealm() { + PropertiesRealm propertiesRealm = new PropertiesRealm(); + propertiesRealm.setResourcePath("classpath:shiro.properties"); + propertiesRealm.setPermissionResolver(new GeodePermissionResolver()); + return propertiesRealm; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java index dcd10d22..70611630 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientServerCacheApplicationIntegrationTests.java @@ -17,17 +17,7 @@ package org.springframework.data.gemfire.config.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.io.IOException; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import static org.assertj.core.api.Assertions.assertThat; import javax.annotation.Resource; @@ -43,18 +33,13 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.data.gemfire.PartitionedRegionFactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; -import org.springframework.data.gemfire.process.ProcessExecutor; import org.springframework.data.gemfire.process.ProcessWrapper; -import org.springframework.data.gemfire.test.support.FileSystemUtils; -import org.springframework.data.gemfire.test.support.IOUtils; -import org.springframework.data.gemfire.test.support.ThreadUtils; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.Assert; +import org.springframework.test.context.junit4.SpringRunner; /** * Test suite of test cases testing the contract and functionality of the {@link CacheServerApplication} @@ -66,15 +51,15 @@ import org.springframework.util.Assert; * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication * @see org.springframework.test.context.ContextConfiguration - * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.junit4.SpringRunner * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.server.CacheServer * @since 1.9.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = ClientServerCacheApplicationIntegrationTests.ClientCacheApplicationConfiguration.class) @SuppressWarnings("all") -public class ClientServerCacheApplicationIntegrationTests { +public class ClientServerCacheApplicationIntegrationTests extends ClientServerIntegrationTestsSupport { private static final int PORT = 12480; @@ -82,56 +67,15 @@ public class ClientServerCacheApplicationIntegrationTests { @BeforeClass public static void setupGemFireServer() throws Exception { - String serverName = ClientServerCacheApplicationIntegrationTests.class.getSimpleName(); + gemfireServerProcess = run(CacheServerApplicationConfiguration.class, String.format("-Dgemfire.name=%1$s", + asApplicationName(ClientServerCacheApplicationIntegrationTests.class))); - File serverWorkingDirectory = new File(FileSystemUtils.WORKING_DIRECTORY, serverName.toLowerCase()); - - Assert.isTrue(serverWorkingDirectory.isDirectory() || serverWorkingDirectory.mkdirs()); - - List arguments = new ArrayList(); - - arguments.add(String.format("-Dgemfire.name=%1$s", serverName)); - - gemfireServerProcess = ProcessExecutor.launch(serverWorkingDirectory, - CacheServerApplicationConfiguration.class, - arguments.toArray(new String[arguments.size()])); - - waitForServerStart(TimeUnit.SECONDS.toMillis(20)); - - System.out.println("GemFire Cache Server Process for Client/Server cache application should be running..."); - } - - private static void waitForServerStart(final long milliseconds) { - ThreadUtils.timedWait(milliseconds, TimeUnit.SECONDS.toMillis(1), new ThreadUtils.WaitCondition() { - AtomicBoolean connected = new AtomicBoolean(false); - - public boolean waiting() { - Socket socket = null; - - try { - if (!connected.get()) { - socket = new Socket("localhost", PORT); - connected.set(true); - } - } - catch (IOException ignore) { - } - finally { - IOUtils.close(socket); - } - - return !connected.get(); - } - }); + waitForServerToStart("localhost", PORT); } @AfterClass public static void tearDownGemFireServer() { - gemfireServerProcess.shutdown(); - - if (Boolean.valueOf(System.getProperty("spring.data.gemfire.fork.clean", Boolean.TRUE.toString()))) { - org.springframework.util.FileSystemUtils.deleteRecursively(gemfireServerProcess.getWorkingDirectory()); - } + stop(gemfireServerProcess); } @Autowired @@ -141,22 +85,19 @@ public class ClientServerCacheApplicationIntegrationTests { private Region echo; @Test - public void clientEchoProxyRegionEchoesKeysForValues() { - assertThat(echo.get("Hello"), is(equalTo("Hello"))); - assertThat(echo.get("Test"), is(equalTo("Test"))); + public void echoClientProxyRegionEchoesKeysForValues() { + assertThat(echo.get("Hello")).isEqualTo("Hello"); + assertThat(echo.get("Test")).isEqualTo("Test"); } - @CacheServerApplication(name = "ClientServerCacheApplicationIntegrationTests", logLevel = "warn", port = 12480) + @CacheServerApplication(name = "ClientServerCacheApplicationIntegrationTests", logLevel = "warn", port = PORT) public static class CacheServerApplicationConfiguration { public static void main(String[] args) { - AnnotationConfigApplicationContext applicationContext = - new AnnotationConfigApplicationContext(CacheServerApplicationConfiguration.class); - - applicationContext.registerShutdownHook(); + runSpringApplication(CacheServerApplicationConfiguration.class, args); } - @Bean(name = "Echo") + @Bean("Echo") public PartitionedRegionFactoryBean echoRegion(Cache gemfireCache) { PartitionedRegionFactoryBean echoRegion = new PartitionedRegionFactoryBean(); @@ -183,7 +124,7 @@ public class ClientServerCacheApplicationIntegrationTests { } } - @ClientCacheApplication(logLevel = "warn", servers = { @ClientCacheApplication.Server(port = 12480)}) + @ClientCacheApplication(logLevel = "warn", servers = { @ClientCacheApplication.Server(port = PORT)}) static class ClientCacheApplicationConfiguration { @Bean(name = "Echo") diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java index 795e5073..b0d4e560 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheApplicationIntegrationTests.java @@ -17,9 +17,7 @@ package org.springframework.data.gemfire.config.annotation; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import javax.annotation.Resource; @@ -33,21 +31,20 @@ import org.junit.runner.RunWith; import org.springframework.context.annotation.Bean; import org.springframework.data.gemfire.PartitionedRegionFactoryBean; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** - * Test suite of test case testing the contract and functionality of the {@link PeerCacheApplication} SDG annotation - * for configuring and bootstrapping a GemFire peer cache instance. + * Integration tests for {@link PeerCacheApplication} SDG annotation. * * @author John Blum * @see org.junit.Test * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication * @see org.springframework.test.context.ContextConfiguration - * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.junit4.SpringRunner * @see org.apache.geode.cache.Cache * @since 1.9.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = PeerCacheApplicationIntegrationTests.PeerCacheApplicationConfiguration.class) @SuppressWarnings("all") public class PeerCacheApplicationIntegrationTests { @@ -57,8 +54,8 @@ public class PeerCacheApplicationIntegrationTests { @Test public void echoPartitionRegionEchoesKeysAsValues() { - assertThat(echo.get("Hello"), is(equalTo("Hello"))); - assertThat(echo.get("Test"), is(equalTo("Test"))); + assertThat(echo.get("Hello")).isEqualTo("Hello"); + assertThat(echo.get("Test")).isEqualTo("Test"); } //@EnableLocator @@ -67,7 +64,7 @@ public class PeerCacheApplicationIntegrationTests { @PeerCacheApplication(name = "PeerCacheApplicationIntegrationTests", logLevel="warn") static class PeerCacheApplicationConfiguration { - @Bean(name = "Echo") + @Bean("Echo") PartitionedRegionFactoryBean echoRegion(Cache gemfireCache) { PartitionedRegionFactoryBean echoRegion = new PartitionedRegionFactoryBean(); diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java index 8869cffb..0bb71420 100644 --- a/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java +++ b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.springframework.data.gemfire.test.support.FileSystemUtils; import org.springframework.util.Assert; @@ -31,6 +32,7 @@ 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 @@ -67,11 +69,7 @@ public abstract class ProcessExecutor { ProcessWrapper processWrapper = new ProcessWrapper(process, ProcessConfiguration.create(processBuilder)); - processWrapper.register(new ProcessInputStreamListener() { - @Override public void onInput(final String input) { - System.err.printf("[FORK-OUT] - %s%n", input); - } - }); + processWrapper.register((input) -> System.err.printf("[FORK] - %s%n", input)); return processWrapper; } @@ -79,7 +77,7 @@ public abstract class ProcessExecutor { 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 command = new ArrayList<>(); List programArgs = Collections.emptyList(); command.add(JAVA_EXE.getAbsolutePath()); @@ -90,7 +88,7 @@ public abstract class ProcessExecutor { command.addAll(getSpringGemFireSystemProperties()); if (args != null) { - programArgs = new ArrayList(args.length); + programArgs = new ArrayList<>(args.length); for (String arg : args) { if (isJvmOption(arg)) { @@ -109,15 +107,10 @@ public abstract class ProcessExecutor { } protected static Collection getSpringGemFireSystemProperties() { - List springGemfireSystemProperties = new ArrayList(); - - for (String property : System.getProperties().stringPropertyNames()) { - if (property.startsWith(SPRING_GEMFIRE_SYSTEM_PROPERTY_PREFIX)) { - springGemfireSystemProperties.add(String.format("-D%1$s=%2$s", property, System.getProperty(property))); - } - } - - return springGemfireSystemProperties; + return System.getProperties().stringPropertyNames().stream() + .filter(property -> 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) { diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java index 522ed325..4b2ceb4e 100644 --- a/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java +++ b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java @@ -18,14 +18,12 @@ package org.springframework.data.gemfire.process; import java.io.BufferedReader; import java.io.File; -import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -61,18 +59,20 @@ public class ProcessWrapper { protected static final long DEFAULT_WAIT_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(15); - private final List listeners = new CopyOnWriteArrayList(); + 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 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; @@ -80,39 +80,42 @@ public class ProcessWrapper { init(); } + /* (non-Javadoc) */ private void init() { - newThread("Process OUT Stream Reader", newProcessInputStreamReader(process.getInputStream())).start(); + newThread("Process OUT Stream Reader Thread", + newProcessInputStreamReaderRunnable(process.getInputStream())).start(); if (!isRedirectingErrorStream()) { - newThread("Process ERR Stream Reader", newProcessInputStreamReader(process.getErrorStream())).start(); + newThread("Process ERR Stream Reader Thread", + newProcessInputStreamReaderRunnable(process.getErrorStream())).start(); } } - protected Runnable newProcessInputStreamReader(final InputStream in) { - return new Runnable() { - @Override public void run() { - if (isRunning()) { - BufferedReader inputReader = new BufferedReader(new InputStreamReader(in)); + /* (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); - } + 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); - } + } + 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"); @@ -125,22 +128,37 @@ public class ProcessWrapper { 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(); @@ -150,22 +168,32 @@ public class ProcessWrapper { } } + /* (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(); @@ -175,38 +203,37 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public String readLogFile() throws IOException { - File[] logFiles = FileSystemUtils.listFiles(getWorkingDirectory(), new FileFilter() { - @Override public boolean accept(File pathname) { - return (pathname != null && (pathname.isDirectory() || pathname.getAbsolutePath().endsWith(".log"))); - } - }); + 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 working directory [%s]", getWorkingDirectory())); + "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(new Runnable() { - @Override public void run() { - shutdown(); - } - })); + Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); } + /* (non-Javadoc) */ public void signalStop() { try { ProcessUtils.signalStop(process); @@ -220,28 +247,28 @@ public class ProcessWrapper { } } + /* (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; - final int pid = safeGetPid(); - final long timeout = (System.currentTimeMillis() + milliseconds); - final AtomicBoolean exited = new AtomicBoolean(false); + int pid = safeGetPid(); + long timeout = (System.currentTimeMillis() + milliseconds); + AtomicBoolean exited = new AtomicBoolean(false); ExecutorService executorService = Executors.newSingleThreadExecutor(); try { - Future futureExitValue = executorService.submit(new Callable() { - @Override public Integer call() throws Exception { - process.destroy(); - int exitValue = process.waitFor(); - exited.set(true); - return exitValue; - } + Future futureExitValue = executorService.submit(() -> { + process.destroy(); + int localExitValue = process.waitFor(); + exited.set(true); + return localExitValue; }); while (!exited.get() && System.currentTimeMillis() < timeout) { @@ -277,6 +304,7 @@ public class ProcessWrapper { } } + /* (non-Javadoc) */ public int shutdown() { if (isRunning()) { log.info(String.format("Stopping process [%d]...%n", safeGetPid())); @@ -287,19 +315,18 @@ public class ProcessWrapper { 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, new ThreadUtils.WaitCondition() { - @Override public boolean waiting() { - return isRunning(); - } - }); + ThreadUtils.timedWait(milliseconds, 500, this::isRunning); } } diff --git a/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java index 03570895..74b5c87d 100644 --- a/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java +++ b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java @@ -54,6 +54,7 @@ public abstract class ProcessUtils { protected static final String TERM_TOKEN = ""; + /* (non-Javadoc) */ public static int currentPid() { RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); String runtimeMXBeanName = runtimeMXBean.getName(); @@ -77,6 +78,12 @@ public abstract class ProcessUtils { 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()) { @@ -87,9 +94,11 @@ public abstract class ProcessUtils { return false; */ + throw new UnsupportedOperationException("operation not supported"); } + /* (non-Javadoc) */ public static boolean isRunning(Process process) { try { process.exitValue(); @@ -100,6 +109,7 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ public static void signalStop(Process process) throws IOException { if (isRunning(process)) { OutputStream processOutputStream = process.getOutputStream(); @@ -108,12 +118,14 @@ public abstract class ProcessUtils { } } + /* (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); @@ -126,6 +138,7 @@ public abstract class ProcessUtils { return readPid(pidFile); } + /* (non-Javadoc) */ @SuppressWarnings("all") protected static File findPidFile(File workingDirectory) { Assert.isTrue(FileSystemUtils.isDirectory(workingDirectory), String.format( @@ -144,6 +157,7 @@ public abstract class ProcessUtils { return null; } + /* (non-Javadoc) */ @SuppressWarnings("all") public static int readPid(File pidFile) { Assert.isTrue(pidFile != null && pidFile.isFile(), String.format( @@ -159,10 +173,10 @@ public abstract class ProcessUtils { return Integer.parseInt(pidValue); } catch (FileNotFoundException e) { - throw new PidUnavailableException(String.format("PID file [%1$s] not found", pidFile), 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 [%1$s]", pidFile), e); + throw new PidUnavailableException(String.format("failed to read PID from file [%s]", pidFile), e); } catch (NumberFormatException e) { throw new PidUnavailableException(String.format( @@ -173,6 +187,7 @@ public abstract class ProcessUtils { } } + /* (non-Javadoc) */ @SuppressWarnings("all") public static void writePid(File pidFile, int pid) throws IOException { Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), String.format( @@ -191,20 +206,28 @@ public abstract class ProcessUtils { } } + /* (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/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java b/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java index 69021418..cbe4c501 100644 --- a/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java +++ b/src/test/java/org/springframework/data/gemfire/test/support/ClientServerIntegrationTestsSupport.java @@ -18,21 +18,32 @@ package org.springframework.data.gemfire.test.support; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.gemfire.util.ArrayUtils.asArray; import java.io.File; import java.io.IOException; import java.net.Socket; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; 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.process.ProcessExecutor; +import org.springframework.data.gemfire.process.ProcessWrapper; +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 org.apache.geode.cache.server.CacheServer + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.data.gemfire.process.ProcessExecutor + * @see org.springframework.data.gemfire.process.ProcessWrapper * @see org.springframework.data.gemfire.test.support.SocketUtils * @see org.springframework.data.gemfire.test.support.ThreadUtils * @since 1.9.0 @@ -41,37 +52,164 @@ import org.apache.geode.cache.server.CacheServer; public class ClientServerIntegrationTestsSupport { protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20); - protected static final long DEFAULT_WAIT_INTERVAL = 500L; + protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds + + protected static final String DEFAULT_GEMFIRE_LOG_FILE = "gemfire-server.log"; + protected static final String DEFAULT_GEMFIRE_LOG_LEVEL = "config"; + protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "sdg.directory.delete-on-exit"; + protected static final String GEMFIRE_LOG_FILE_PROPERTY = "gemfire.log.file"; + protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "gemfire.log.level"; + protected static final String PROCESS_RUN_MANUAL_PROPERTY = "sdg.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) { - File directory = new File(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(); - directory.deleteOnExit(); + if (isDeleteDirectoryOnExit()) { + directory.deleteOnExit(); + } return directory; } + /* (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(DEFAULT_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(DEFAULT_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 (isProcessRunManual() ? null + : ProcessExecutor.launch(createDirectory(workingDirectory), type, arguments)); + } + + /* (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 (isProcessRunManual() ? null + : ProcessExecutor.launch(createDirectory(workingDirectory), classpath, type, arguments)); + } + + /* (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) { + if (process != null) { + process.stop(duration); + + if (process.isNotRunning() && isDeleteDirectoryOnExit()) { + FileSystemUtils.deleteRecursive(process.getWorkingDirectory()); + } + + return process.isRunning(); + } + + return true; + } + /* (non-Javadoc) */ protected static boolean waitForCacheServerToStart(CacheServer cacheServer) { - return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION); + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION); } /* (non-Javadoc) */ protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) { - return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); + return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); } /* (non-Javadoc) */ - protected static boolean waitForCacheServerToStart(String host, int port) { - return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION); + protected static boolean waitForServerToStart(String host, int port) { + return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION); } /* (non-Javadoc) */ - protected static boolean waitForCacheServerToStart(final String host, final int port, long duration) { + protected static boolean waitForServerToStart(final String host, final int port, long duration) { return ThreadUtils.timedWait(duration, DEFAULT_WAIT_INTERVAL, new ThreadUtils.WaitCondition() { AtomicBoolean connected = new AtomicBoolean(false); diff --git a/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java index f3f9133f..7f57bdb4 100644 --- a/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/ArrayUtilsUnitTests.java @@ -32,11 +32,6 @@ import org.junit.Test; */ public class ArrayUtilsUnitTests { - @SafeVarargs - protected static T[] asArray(T... array) { - return array; - } - @Test public void asArrayRetursEmptyArray() { Object[] array = ArrayUtils.asArray(); @@ -65,12 +60,13 @@ public class ArrayUtilsUnitTests { @Test public void getFirstWithNonNullArray() { - assertThat(ArrayUtils.getFirst(asArray(1, 2, 3))).isEqualTo(1); + assertThat(ArrayUtils.getFirst(ArrayUtils.asArray(1, 2, 3))).isEqualTo(1); } @Test public void getFirstWithNullOrEmptyArrayAndNoDefaultReturnsNull() { assertThat((Object) ArrayUtils.getFirst(null)).isNull(); + assertThat((Object) ArrayUtils.getFirst(new Object[0])).isNull(); } @Test diff --git a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java index 7e95a9eb..32449130 100644 --- a/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/util/CollectionUtilsUnitTests.java @@ -181,7 +181,7 @@ public class CollectionUtilsUnitTests { assertThat(iterable.iterator().hasNext()).isFalse(); } - @Test(expected = UnsupportedOperationException.class) + @Test(expected = IllegalStateException.class) public void nullSafeIterableIterator() { Iterable iterable = CollectionUtils.nullSafeIterable(null); @@ -196,14 +196,12 @@ public class CollectionUtilsUnitTests { iterator.next(); } catch (NoSuchElementException ignore) { - assertThat(ignore.getMessage()).isEqualTo("No more elements"); assertThat(ignore.getCause()).isNull(); try { iterator.remove(); } - catch (UnsupportedOperationException expected) { - assertThat(expected.getMessage()).isEqualTo("Operation not supported"); + catch (IllegalStateException expected) { assertThat(expected.getCause()).isNull(); throw expected; diff --git a/src/test/resources/shiro.ini b/src/test/resources/shiro.ini new file mode 100644 index 00000000..2cd25350 --- /dev/null +++ b/src/test/resources/shiro.ini @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Shiro INI configuration + +# Objects and their properties are defined here, such as the securityManager, Realms and anything else needed +# to build the SecurityManager +[main] + +# The 'users' section is for simple deployments when you only need a small number of statically-defined +# set of User accounts. +# username = password, roleName1, roleName2, …, roleNameN +[users] +root = s3cr3t!, ADMIN, DBA +scientist = w0rk!ng4u, DATA_SCIENTIST +analyst = p@55w0rd, DATA_ANALYST +guest = guest, GUEST + +# The 'roles' section is for simple deployments when you only need a small number of statically-defined roles. +# rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN +[roles] +ADMIN = CLUSTER:MANAGE, CLUSTER:READ, CLUSTER:WRITE +DBA = DATA:MANAGE, DATA:READ, DATA:WRITE +DATA_SCIENTIST = DATA:READ, DATA:WRITE +DATA_ANALYST = DATA:READ +GUEST = NULL + +# The 'urls' section is used for url-based security in web applications. We'll discuss this section +# in the Web documentation +[urls] diff --git a/src/test/resources/shiro.properties b/src/test/resources/shiro.properties new file mode 100644 index 00000000..98bfa6f3 --- /dev/null +++ b/src/test/resources/shiro.properties @@ -0,0 +1,32 @@ +# +# 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. +# +# + +# Apache Shiro PropertiesRealm security properties configuration file +# used to configure Apache Geode Security + +# Apache Geode System Users and assigned Roles +user.root = s3c3rt!, ADMIN, DBA +user.scientist = w0rk!ng4u, DATA_SCIENTIST +user.analyst = p@55w0rd, DATA_ANALYST +user.guest = guest, GUEST + +# Apache Geode Roles and granted (Resource) Permissions +role.ADMIN = CLUSTER:MANAGE, CLUSTER:READ, CLUSTER:WRITE +role.DBA = DATA:MANAGE, DATA:READ, DATA:WRITE +role.DATA_SCIENTIST = DATA:READ, DATA:WRITE +role.DATA_ANALYST = DATA:READ +role.GUEST = NULL diff --git a/template.mf b/template.mf index cc3dfcbc..a151573e 100644 --- a/template.mf +++ b/template.mf @@ -3,13 +3,14 @@ Bundle-Name: Spring Data GemFire Bundle-SymbolicName: org.springframework.data.gemfire Bundle-Vendor: Pivotal Software, Inc. Import-Package: sun.reflect;version="0";resolution:=optional -Import-Template: javax.enterprise.*;version="[1.0,2.0)";resolution:=optional, +Import-Template: javax.enterprise.*;version="[1.0, 2.0)";resolution:=optional, org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", - org.apache.geode.*;version="[8.0.0,9.0.0)", - org.aspectj.*;version="[1.8.2, 2.0.0)";resolution:=optional, - com.fasterxml.jackson.*;version="[2.4.1,3.0.0)";resolution:=optional, - org.slf4j.*;version="[1.7.0,2.0)", - org.springframework.*;version="[4.0.6, 5.0.0)", - org.springframework.data.*;version="[1.9.0,2.0.0)", + org.apache.geode.*;version="[0.0.0, 10.0.0)", + org.apache.shiro.*;version="[1.3.1, 2.0.0)";resolution:=optional, + org.aspectj.*;version="[1.8.9, 2.0.0)";resolution:=optional, + com.fasterxml.jackson.*;version="[2.8.4, 3.0.0)";resolution:=optional, + org.slf4j.*;version="[1.7.21, 2.0)", + org.springframework.*;version="[5.0.0, 6.0.0)", + org.springframework.data.*;version="[2.0.0, 3.0.0)", org.w3c.dom.*;version="0"